+2017-04-19 Pedro Alves <palves@redhat.com>
+
+ * common/refcounted-object.h: New file.
+ * gdbthread.h: Include "common/refcounted-object.h".
+ (thread_info): Inherit from refcounted_object and add comments.
+ (thread_info::incref, thread_info::decref)
+ (thread_info::m_refcount): Delete.
+ (thread_info::deletable): Use the refcounted_object::refcount()
+ method.
+ * inferior.c (current_inferior_): Add comment.
+ (set_current_inferior): Increment/decrement refcounts.
+ (prune_inferiors, remove_inferior_command): Skip inferiors marked
+ not-deletable instead of comparing with the current inferior.
+ (initialize_inferiors): Increment the initial inferior's refcount.
+ * inferior.h (struct inferior): Forward declare.
+ Include "common/refcounted-object.h".
+ (current_inferior, set_current_inferior): Move declaration to
+ before struct inferior's definition, and fix comment.
+ (inferior): Inherit from refcounted_object. Add comments.
+ * thread.c (switch_to_thread_no_regs): Reference the thread's
+ inferior pointer directly instead of doing a ptid lookup.
+ (switch_to_no_thread): New function.
+ (switch_to_thread(thread_info *)): New function, factored out
+ from ...
+ (switch_to_thread(ptid_t)): ... this.
+ (restore_current_thread): Delete.
+ (current_thread_cleanup): Remove 'inf_id' and 'was_removable'
+ fields, and add 'inf' field.
+ (do_restore_current_thread_cleanup): Check whether old->inf is
+ alive instead of looking up an inferior by ptid. Use
+ switch_to_thread and switch_to_no_thread.
+ (restore_current_thread_cleanup_dtor): Use old->inf directly
+ instead of lookup up an inferior by id. Decref the inferior.
+ Don't restore 'removable'.
+ (make_cleanup_restore_current_thread): Same the inferior pointer
+ in old, instead of the inferior number. Incref the inferior.
+ Don't save/clear 'removable'.
+
2017-04-19 Pedro Alves <palves@redhat.com>
* Makefile.in (SUBDIR_UNITTESTS_SRCS): Add
--- /dev/null
+/* Base class of intrusively reference-counted objects.
+ Copyright (C) 2017 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ 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/>. */
+
+#ifndef REFCOUNTED_OBJECT_H
+#define REFCOUNTED_OBJECT_H
+
+/* Base class of intrusively reference-countable objects.
+ Incrementing and decrementing the reference count is an external
+ responsibility. */
+
+class refcounted_object
+{
+public:
+ refcounted_object () = default;
+
+ /* Increase the refcount. */
+ void incref ()
+ {
+ gdb_assert (m_refcount >= 0);
+ m_refcount++;
+ }
+
+ /* Decrease the refcount. */
+ void decref ()
+ {
+ m_refcount--;
+ gdb_assert (m_refcount >= 0);
+ }
+
+ int refcount () const { return m_refcount; }
+
+private:
+ /* Disable copy. */
+ refcounted_object (const refcounted_object &) = delete;
+ refcounted_object &operator=(const refcounted_object &) = delete;
+
+ /* The reference count. */
+ int m_refcount = 0;
+};
+
+#endif /* REFCOUNTED_OBJECT_H */
#include "common/vec.h"
#include "target/waitstatus.h"
#include "cli/cli-utils.h"
+#include "common/refcounted-object.h"
/* Frontend view of the thread state. Possible extensions: stepping,
finishing, until(ling),... */
DEF_VEC_P (value_ptr);
typedef VEC (value_ptr) value_vec;
-struct thread_info
+/* Threads are intrusively refcounted objects. Being the
+ user-selected thread is normally considered an implicit strong
+ reference and is thus not accounted in the refcount, unlike
+ inferior objects. This is necessary, because there's no "current
+ thread" pointer. Instead the current thread is inferred from the
+ inferior_ptid global. However, when GDB needs to remember the
+ selected thread to later restore it, GDB bumps the thread object's
+ refcount, to prevent something deleting the thread object before
+ reverting back (e.g., due to a "kill" command. If the thread
+ meanwhile exits before being re-selected, then the thread object is
+ left listed in the thread list, but marked with state
+ THREAD_EXITED. (See make_cleanup_restore_current_thread and
+ delete_thread). All other thread references are considered weak
+ references. Placing a thread in the thread list is an implicit
+ strong reference, and is thus not accounted for in the thread's
+ refcount. */
+
+class thread_info : public refcounted_object
{
public:
explicit thread_info (inferior *inf, ptid_t ptid);
bool deletable () const
{
/* If this is the current thread, or there's code out there that
- relies on it existing (m_refcount > 0) we can't delete yet. */
- return (m_refcount == 0 && !ptid_equal (ptid, inferior_ptid));
- }
-
- /* Increase the refcount. */
- void incref ()
- {
- gdb_assert (m_refcount >= 0);
- m_refcount++;
- }
-
- /* Decrease the refcount. */
- void decref ()
- {
- m_refcount--;
- gdb_assert (m_refcount >= 0);
+ relies on it existing (refcount > 0) we can't delete yet. */
+ return (refcount () == 0 && !ptid_equal (ptid, inferior_ptid));
}
struct thread_info *next = NULL;
fields point to self. */
struct thread_info *step_over_prev = NULL;
struct thread_info *step_over_next = NULL;
-
-private:
-
- /* If this is > 0, then it means there's code out there that relies
- on this thread being listed. Don't delete it from the lists even
- if we detect it exiting. */
- int m_refcount = 0;
};
/* Create an empty thread list, or empty the existing one. */
`set print inferior-events'. */
static int print_inferior_events = 0;
-/* The Current Inferior. */
+/* The Current Inferior. This is a strong reference. I.e., whenever
+ an inferior is the current inferior, its refcount is
+ incremented. */
static struct inferior *current_inferior_ = NULL;
struct inferior*
/* There's always an inferior. */
gdb_assert (inf != NULL);
+ inf->incref ();
+ current_inferior_->decref ();
current_inferior_ = inf;
}
prune_inferiors (void)
{
struct inferior *ss, **ss_link;
- struct inferior *current = current_inferior ();
ss = inferior_list;
ss_link = &inferior_list;
while (ss)
{
- if (ss == current
+ if (!ss->deletable ()
|| !ss->removable
|| ss->pid != 0)
{
continue;
}
- if (inf == current_inferior ())
+ if (!inf->deletable ())
{
warning (_("Can not remove current inferior %d."), num);
continue;
that. Do this after initialize_progspace, due to the
current_program_space reference. */
current_inferior_ = add_inferior (0);
+ current_inferior_->incref ();
current_inferior_->pspace = current_program_space;
current_inferior_->aspace = current_program_space->aspace;
/* The architecture will be initialized shortly, by
struct target_desc_info;
struct gdb_environ;
struct continuation;
+struct inferior;
/* For bpstat. */
#include "breakpoint.h"
#include "registry.h"
#include "symfile-add-flags.h"
+#include "common/refcounted-object.h"
struct infcall_suspend_state;
struct infcall_control_state;
enum stop_kind stop_soon;
};
+/* Return a pointer to the current inferior. */
+extern inferior *current_inferior ();
+
+extern void set_current_inferior (inferior *);
+
/* GDB represents the state of each program execution with an object
called an inferior. An inferior typically corresponds to a process
but is more general and applies also to targets that do not have a
inferior, as does each attachment to an existing process.
Inferiors have unique internal identifiers that are different from
target process ids. Each inferior may in turn have multiple
- threads running in it. */
-
-class inferior
+ threads running in it.
+
+ Inferiors are intrusively refcounted objects. Unlike thread
+ objects, being the user-selected inferior is considered a strong
+ reference and is thus accounted for in the inferior object's
+ refcount (see set_current_inferior). When GDB needs to remember
+ the selected inferior to later restore it, GDB temporarily bumps
+ the inferior object's refcount, to prevent something deleting the
+ inferior object before reverting back (e.g., due to a
+ "remove-inferiors" command (see
+ make_cleanup_restore_current_thread). All other inferior
+ references are considered weak references. Inferiors are always
+ listed exactly once in the inferior list, so placing an inferior in
+ the inferior list is an implicit, not counted strong reference. */
+
+class inferior : public refcounted_object
{
public:
explicit inferior (int pid);
~inferior ();
+ /* Returns true if we can delete this inferior. */
+ bool deletable () const { return refcount () == 0; }
+
/* Pointer to next inferior in singly-linked list of inferiors. */
struct inferior *next = NULL;
(not cores, not executables, real live processes). */
extern int have_live_inferiors (void);
-/* Return a pointer to the current inferior. It is an error to call
- this if there is no current inferior. */
-extern struct inferior *current_inferior (void);
-
-extern void set_current_inferior (struct inferior *);
-
extern struct cleanup *save_current_inferior (void);
/* Traverse all inferiors. */
+2017-04-19 Pedro Alves <palves@redhat.com>
+
+ * gdb.threads/threadapply.exp (kill_and_remove_inferior): New
+ procedure.
+ (top level): Call it.
+ * lib/gdb.exp (gdb_define_cmd): New procedure.
+
2017-04-12 Pedro Alves <palves@redhat.com>
PR gdb/21323
foreach thread_set {"all" "1.1 1.2 1.3"} {
thr_apply_detach $thread_set
}
+
+# Test killing and removing inferiors from a command run via "thread
+# apply THREAD_SET". THREAD_SET can be either "1.1", or "all". GDB
+# used to mistakenly allow deleting the previously-selected inferior,
+# in some cases, leading to crashes.
+
+proc kill_and_remove_inferior {thread_set} {
+ global binfile
+ global gdb_prompt
+
+ # The test starts multiple inferiors, therefore non-extended
+ # remote is not supported.
+ if [target_info exists use_gdb_stub] {
+ unsupported "using gdb stub"
+ return
+ }
+
+ set any "\[^\r\n\]*"
+ set ws "\[ \t\]\+"
+
+ clean_restart ${binfile}
+
+ with_test_prefix "start inferior 1" {
+ runto_main
+ }
+
+ # Add and start inferior number NUM.
+ proc add_and_start_inferior {num} {
+ global binfile
+
+ # Start another inferior.
+ gdb_test "add-inferior" "Added inferior $num.*" \
+ "add empty inferior $num"
+ gdb_test "inferior $num" "Switching to inferior $num.*" \
+ "switch to inferior $num"
+ gdb_test "file ${binfile}" ".*" "load file in inferior $num"
+
+ with_test_prefix "start inferior $num" {
+ runto_main
+ }
+ }
+
+ # Start another inferior.
+ add_and_start_inferior 2
+
+ # And yet another.
+ add_and_start_inferior 3
+
+ gdb_test "thread 2.1" "Switching to thread 2.1 .*"
+
+ # Try removing an active inferior via a "thread apply" command.
+ # Should fail/warn.
+ with_test_prefix "try remove" {
+
+ gdb_define_cmd "remove" {
+ "remove-inferiors 3"
+ }
+
+ # Inferior 3 is still alive, so can't remove it.
+ gdb_test "thread apply $thread_set remove" \
+ "warning: Can not remove active inferior 3.*"
+ # Check that GDB restored the selected thread.
+ gdb_test "thread" "Current thread is 2.1 .*"
+
+ gdb_test "info inferiors" \
+ [multi_line \
+ "${ws}1${ws}process ${any}" \
+ "\\\* 2${ws}process ${any}" \
+ "${ws}3${ws}process ${any}" \
+ ]
+ }
+
+ # Kill and try to remove inferior 2 while inferior 2 is selected.
+ # Removing the inferior should fail/warn.
+ with_test_prefix "try kill-and-remove" {
+
+ # The "inferior 1" command works around PR gdb/19318 ("kill
+ # inferior N" shouldn't switch to inferior N).
+ gdb_define_cmd "kill-and-remove" {
+ "kill inferiors 2"
+ "inferior 1"
+ "remove-inferiors 2"
+ }
+
+ # Note that when threads=1.1, this makes sure we're really
+ # testing failure to remove the inferior the user had selected
+ # before the "thread apply" command, instead of testing
+ # refusal to remove the currently-iterated inferior.
+ gdb_test "thread apply $thread_set kill-and-remove" \
+ "warning: Can not remove current inferior 2.*"
+ gdb_test "thread" "No thread selected" \
+ "switched to no thread selected"
+
+ gdb_test "info inferiors" \
+ [multi_line \
+ "${ws}1${ws}process ${any}" \
+ "\\\* 2${ws}<null>${any}" \
+ "${ws}3${ws}process ${any}" \
+ ]
+ }
+
+ # Try removing (the now dead) inferior 2 while inferior 1 is
+ # selected. This should succeed.
+ with_test_prefix "try remove 2" {
+
+ gdb_test "thread 1.1" "Switching to thread 1.1 .*"
+
+ gdb_define_cmd "remove-again" {
+ "remove-inferiors 2"
+ }
+
+ set test "thread apply $thread_set remove-again"
+ gdb_test_multiple $test $test {
+ -re "warning: Can not remove.*$gdb_prompt $" {
+ fail $test
+ }
+ -re "$gdb_prompt $" {
+ pass $test
+ }
+ }
+ gdb_test "thread" "Current thread is 1.1 .*"
+ # Check that only inferiors 1 and 3 are around.
+ gdb_test "info inferiors" \
+ [multi_line \
+ "\\\* 1${ws}process ${any}" \
+ "${ws}3${ws}process ${any}" \
+ ]
+ }
+}
+
+# Test both "all" and a thread list, because those are implemented as
+# different commands in GDB.
+foreach_with_prefix thread_set {"all" "1.1"} {
+ kill_and_remove_inferior $thread_set
+}
return $dg_ver
}
+# Define user-defined command COMMAND using the COMMAND_LIST as the
+# command's definition. The terminating "end" is added automatically.
+
+proc gdb_define_cmd {command command_list} {
+ global gdb_prompt
+
+ set input [multi_line_input {*}$command_list "end"]
+ set test "define $command"
+
+ gdb_test_multiple "define $command" $test {
+ -re "End with" {
+ gdb_test_multiple $input $test {
+ -re "\r\n$gdb_prompt " {
+ }
+ }
+ }
+ }
+}
+
# Always load compatibility stuff.
load_lib future.exp
static int thread_alive (struct thread_info *);
static void info_threads_command (char *, int);
static void thread_apply_command (char *, int);
-static void restore_current_thread (ptid_t);
/* RAII type used to increase / decrease the refcount of each thread
in a given list of threads. */
void
switch_to_thread_no_regs (struct thread_info *thread)
{
- struct inferior *inf;
+ struct inferior *inf = thread->inf;
- inf = find_inferior_ptid (thread->ptid);
- gdb_assert (inf != NULL);
set_current_program_space (inf->pspace);
set_current_inferior (inf);
stop_pc = ~(CORE_ADDR) 0;
}
-/* Switch from one thread to another. */
+/* Switch to no thread selected. */
-void
-switch_to_thread (ptid_t ptid)
+static void
+switch_to_no_thread ()
{
- /* Switch the program space as well, if we can infer it from the now
- current thread. Otherwise, it's up to the caller to select the
- space it wants. */
- if (ptid != null_ptid)
- {
- struct inferior *inf;
+ if (inferior_ptid == null_ptid)
+ return;
- inf = find_inferior_ptid (ptid);
- gdb_assert (inf != NULL);
- set_current_program_space (inf->pspace);
- set_current_inferior (inf);
- }
+ inferior_ptid = null_ptid;
+ reinit_frame_cache ();
+ stop_pc = ~(CORE_ADDR) 0;
+}
+
+/* Switch from one thread to another. */
- if (ptid == inferior_ptid)
+static void
+switch_to_thread (thread_info *thr)
+{
+ gdb_assert (thr != NULL);
+
+ if (inferior_ptid == thr->ptid)
return;
- inferior_ptid = ptid;
+ switch_to_thread_no_regs (thr);
+
reinit_frame_cache ();
/* We don't check for is_stopped, because we're called at times
while in the TARGET_RUNNING state, e.g., while handling an
internal event. */
- if (inferior_ptid != null_ptid
- && !is_exited (ptid)
- && !is_executing (ptid))
- stop_pc = regcache_read_pc (get_thread_regcache (ptid));
- else
- stop_pc = ~(CORE_ADDR) 0;
+ if (thr->state != THREAD_EXITED
+ && !thr->executing)
+ stop_pc = regcache_read_pc (get_thread_regcache (thr->ptid));
}
-static void
-restore_current_thread (ptid_t ptid)
+/* See gdbthread.h. */
+
+void
+switch_to_thread (ptid_t ptid)
{
- switch_to_thread (ptid);
+ if (ptid == null_ptid)
+ switch_to_no_thread ();
+ else
+ switch_to_thread (find_thread_ptid (ptid));
}
static void
struct frame_id selected_frame_id;
int selected_frame_level;
int was_stopped;
- int inf_id;
- int was_removable;
+ inferior *inf;
};
static void
/* If the previously selected thread belonged to a process that has
in the mean time exited (or killed, detached, etc.), then don't revert
back to it, but instead simply drop back to no thread selected. */
- && find_inferior_ptid (old->thread->ptid) != NULL)
- restore_current_thread (old->thread->ptid);
+ && old->inf->pid != 0)
+ switch_to_thread (old->thread);
else
{
- restore_current_thread (null_ptid);
- set_current_inferior (find_inferior_id (old->inf_id));
+ switch_to_no_thread ();
+ set_current_inferior (old->inf);
}
/* The running state of the originally selected thread may have
restore_current_thread_cleanup_dtor (void *arg)
{
struct current_thread_cleanup *old = (struct current_thread_cleanup *) arg;
- struct thread_info *tp;
- struct inferior *inf;
if (old->thread != NULL)
old->thread->decref ();
- inf = find_inferior_id (old->inf_id);
- if (inf != NULL)
- inf->removable = old->was_removable;
+ old->inf->decref ();
xfree (old);
}
struct current_thread_cleanup *old = XNEW (struct current_thread_cleanup);
old->thread = NULL;
- old->inf_id = current_inferior ()->num;
- old->was_removable = current_inferior ()->removable;
+ old->inf = current_inferior ();
if (inferior_ptid != null_ptid)
{
old->thread = tp;
}
- current_inferior ()->removable = 0;
+ old->inf->incref ();
return make_cleanup_dtor (do_restore_current_thread_cleanup, old,
restore_current_thread_cleanup_dtor);