From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail-ej1-x629.google.com (mail-ej1-x629.google.com [IPv6:2a00:1450:4864:20::629]) by sourceware.org (Postfix) with ESMTPS id D72DF385701F for ; Tue, 23 Mar 2021 11:13:40 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.3.2 sourceware.org D72DF385701F Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=embecosm.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=andrew.burgess@embecosm.com Received: by mail-ej1-x629.google.com with SMTP id r12so26414838ejr.5 for ; Tue, 23 Mar 2021 04:13:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=embecosm.com; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=58szwHjsMRrqU2hqCNSYADlp1Z4If6+mrgvaMyVs7Rg=; b=VRnBy/N+p51PPfr2J9hXHVIDPEkiqpdDdZI4XYu9rxtCtIu+PA9drVVtlMPxJWJE5/ bDVQ++AK3tzHGOac9Rthp5SmWbvHFeT9RRQGpDS++IHe9VsxeDrQOnLC/Wt3hWwc3OHD X/88nyJYM8Ed58+2ULli9J1MzKxdmy5hlJgAaGaCo8rAInD+yx126bcLIvaSBkZgFhCB wlRLZiBLEP7x8hcYaCfqcySzHLkVoCxdvzICNWIRBIpYR+XQFXuKkMQtkJhF/2xa+QRR 6fAmuVjMT7xerTYWW0VSmvv1TkxlaMWNlaR/4BinuaDGexsn/CaYvgcgrrByn4m2rXVy Yvaw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=58szwHjsMRrqU2hqCNSYADlp1Z4If6+mrgvaMyVs7Rg=; b=MptVgJwFmuURov7lQuY+pg6IHuxMqGpAgiqOZvzmYbUPLcDyjgj8imFAb2WPQbaQzE IkUxXuF/S7RqQz/DXXJ5U9Cfi8J3T18IxQslQ//k/M0AkUek75QX+m6TyeAHCc0uoxqJ Yg5xJp/2vomK0lnhFT4kJpBsPEpg+s/wSuaBta2eO9Wm8gRKal69dQdb5Kd6Hq44qANj Fy0DMYgx3fbvsyiptfDjX8UQL08gdYP+uIq47KEpa3Acw8cVqZLvbLJUqJhE53wt6ppu esHTyN/cxUtNLi1UR4UlI/TvOptlhT9SgDKcCrC0kI7Yxe1tS9czQCrzkIn8ii4yqXkg tj2Q== X-Gm-Message-State: AOAM530W+hBSSC2FJwzWJXN0C5scb3TC4u4dXrQOVREcDofWNeU3GiB8 +jObFhOciLAVcTV1OXxQ3ohXPNA6MBrjoQ== X-Google-Smtp-Source: ABdhPJwp4yXTm0oXBbRvvLX6WDPhAwhftgjFtUKUZgFsc+/jG/0s6U4fp/JPnV8QDRudMJVdURdQug== X-Received: by 2002:a17:906:6044:: with SMTP id p4mr4458702ejj.82.1616498019024; Tue, 23 Mar 2021 04:13:39 -0700 (PDT) Received: from localhost (host165-120-118-227.range165-120.btcentralplus.com. [165.120.118.227]) by smtp.gmail.com with ESMTPSA id bx24sm11244879ejc.88.2021.03.23.04.13.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 23 Mar 2021 04:13:38 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Subject: [PATCHv8] gdb: Restore previously selected thread when switching inferior Date: Tue, 23 Mar 2021 11:13:36 +0000 Message-Id: <20210323111336.149124-1-andrew.burgess@embecosm.com> X-Mailer: git-send-email 2.25.4 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-11.9 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.2 X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on server2.sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 23 Mar 2021 11:13:45 -0000 Given Pedro's reluctance to give any feedback on my previous iteration of the patch I thought I'd have an attempt at a completely different approach. I have now converted the first of the changes to be a Python extension. This did require an additional observer / Python event, but maybe this is considered OK? If the feedback on this is generally positive I would probably look at converting the second patch along similar lines. Thanks, Andrew --- This commit adds a new option that allows the user to control how GDB behaves when switching between multi-threaded inferiors. Currently (and this remains the default after this commit) when switching between inferiors GDB would select the first non-exited thread from the inferior being switched to. This commit adds the following new commands: set restore-selected-thread on|off show restore-selected-thread This option is off by default in order to retain the existing behaviour, but, when switched on GDB will remember which thread was selected in each inferior. As the user switches between inferiors GDB will attempt to restore the previously selected thread. If the previously selected thread is no longer available, for example, if the thread has exited, then GDB will fall back on the old behaviour. I've previously attempted to upstream this functionality, these attempts can be found: https://sourceware.org/pipermail/gdb-patches/2020-April/167985.html https://sourceware.org/pipermail/gdb-patches/2020-September/172135.html https://sourceware.org/pipermail/gdb-patches/2020-November/173068.html https://sourceware.org/pipermail/gdb-patches/2020-November/173199.html https://sourceware.org/pipermail/gdb-patches/2020-December/173968.html https://sourceware.org/pipermail/gdb-patches/2021-February/175973.html The previous attempt has stalled as it needed to get approval from Pedro, who unfortunately has so far been unable to provide feedback on the later versions of the patch. This patch is a brand new approach to solving this problem. I have now implemented the command as a Python extension. We currently have the user_selected_context_changed observer that is triggered at the right spot in GDB to allow me to implement this patch in pure Python. The problem with this observer is that it is used as the hook by which GDB displays the 'Switching to inferior ....' and 'Switching to thread ...' messages. I need to ensure that Python gets a chance to observe the inferior change before these messages are displayed. I see two possible solutions, one choice is to allow a basic level of ordering when attaching to an observer. In this way when the Python code attaches to user_selected_context_changed it could request to be called early on. The hooks that print the messages can request that they are called later. The observer object would be in charge of keeping the attached observers sorted. The other, possibly simpler choice, is to just add a new observer that is called when the user switches inferior. I chose the second approach, though I'd be happy to investigate the first approach if folk think it would be a better choice. I've installed the Python code in the data-directory so that it is auto-loaded into a users session, as the restore behaviour is off by default this hopefully shouldn't be too invasive. There's a new test for this functionality. gdb/ChangeLog: * NEWS: Mention new feature and new Python event. * data-directory/Makefile.in (PYTHON_FILE_LIST): Add restore-thread.py. * inferior.c (switch_to_inferior_no_thread): Move declaration of some variables into more inner block. Get the current inferior in the top function scope. After switching inferior, call the new observable. * observable.c (inferior_changed): Define new observable. * observable.h (inferior_changed): Declare new observable. * python/lib/gdb/command/restore-thread.py: New file. * python/py-all-events.def (inferior_changed): Declare. * python/py-event-types.def (inferior_changed): Declare. * python/py-inferior.c (python_inferior_changed): New function. (gdbpy_initialize_inferior): Register with inferior_changed observer. gdb/doc/ChangeLog: * gdb.texinfo (Inferiors Connections and Programs): Mention thread tracking within the inferior command. (Threads): Mention thread tracking in the general thread discussion. * python.texinfo (Events In Python): Document new gdb.InferiorChangedEvent. gdb/testsuite/ChangeLog: * gdb.threads/restore-thread.c: New file. * gdb.threads/restore-thread.exp: New file. --- gdb/ChangeLog | 18 ++ gdb/NEWS | 14 ++ gdb/data-directory/Makefile.in | 1 + gdb/doc/ChangeLog | 9 + gdb/doc/gdb.texinfo | 19 +- gdb/doc/python.texi | 35 +++ gdb/inferior.c | 18 +- gdb/observable.c | 1 + gdb/observable.h | 12 + gdb/python/lib/gdb/command/restore-thread.py | 107 ++++++++ gdb/python/py-all-events.def | 1 + gdb/python/py-event-types.def | 5 + gdb/python/py-inferior.c | 65 +++++ gdb/testsuite/ChangeLog | 5 + gdb/testsuite/gdb.threads/restore-thread.c | 248 +++++++++++++++++++ gdb/testsuite/gdb.threads/restore-thread.exp | 219 ++++++++++++++++ 16 files changed, 771 insertions(+), 6 deletions(-) create mode 100644 gdb/python/lib/gdb/command/restore-thread.py create mode 100644 gdb/testsuite/gdb.threads/restore-thread.c create mode 100644 gdb/testsuite/gdb.threads/restore-thread.exp diff --git a/gdb/NEWS b/gdb/NEWS index 7f5a745d0c0..a0609aa55a6 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -50,6 +50,15 @@ maintenance flush dcache maintenance info target-sections Print GDB's internal target sections table. +set restore-selected-thread on|off +show restore-selected-thread + When turned on, GDB will record the currently selected thread in + each inferior. When switching between inferiors, GDB will attempt + to restore the previously selected thread in the inferior being + switched too. If the previously selected thread is no longer + available, then GDB falls back to selecting the first non-exited + thread. + * Changed commands break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM] @@ -98,6 +107,11 @@ maintenance info sections ARM Symbian arm*-*-symbianelf* +* Python API + + ** New gdb.InferiorChangedEvent which is emitted when the user + requests a change of the currently selected inferior. + *** Changes in GDB 10 * There are new feature names for ARC targets: "org.gnu.gdb.arc.core" diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in index 8b65790cdd9..6b9d65698ed 100644 --- a/gdb/data-directory/Makefile.in +++ b/gdb/data-directory/Makefile.in @@ -82,6 +82,7 @@ PYTHON_FILE_LIST = \ gdb/command/frame_filters.py \ gdb/command/pretty_printers.py \ gdb/command/prompt.py \ + gdb/command/restore-thread.py \ gdb/command/type_printers.py \ gdb/command/unwinders.py \ gdb/command/xmethods.py \ diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 80ccf74a049..18fe1db0e48 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -3179,11 +3179,25 @@ To switch focus between inferiors, use the @code{inferior} command: @table @code +@anchor{inferior command} @kindex inferior @var{infno} @item inferior @var{infno} Make inferior number @var{infno} the current inferior. The argument @var{infno} is the inferior number assigned by @value{GDBN}, as shown in the first field of the @samp{info inferiors} display. + +When switching between inferiors with multiple threads (@pxref{Threads}), +@value{GDBN} will select the first non-exited thread in the inferior being +switched to, and make this the current thread. + +@kindex set restore-selected-thread +@kindex show restore-selected-thread +@item set restore-selected-thread @r{[}on|off@r{]} +@item show restore-selected-thread +When this option is on, @value{GDBN} will record the currently selected +thread in each inferior. When switching between inferiors, @value{GDBN} +will try to restore the previously selected thread in the inferior being +switched to. This option is off by default. @end table @vindex $_inferior@r{, convenience variable} @@ -3565,7 +3579,10 @@ If you're debugging multiple inferiors, @value{GDBN} displays thread IDs using the qualified @var{inferior-num}.@var{thread-num} format. -Otherwise, only @var{thread-num} is shown. +Otherwise, only @var{thread-num} is shown. When switching between +inferiors, @value{GDBN} will select a suitable thread in the inferior +being switched to, see @ref{inferior command,,the @code{inferior} +command}, for further details on how to control this behaviour. If you specify the @samp{-gid} option, @value{GDBN} displays a column indicating each thread's global thread ID: diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index 9135d415dd1..b9c0028cec8 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -3314,6 +3314,41 @@ The new thread. @end defvar +@item events.inferior_changed +This is emitted when the user requests @value{GDBN} change the +currently selected inferior. The event is of type +@code{gdb.InferiorChangedEvent}, and has the following attributes: + +@defvar InferiorChangedEvent.from_inferior +The gdb.Inferior (@pxref{Inferiors In Python}) that used to be +selected, but no longer is. +@end defvar + +@defvar InferiorChangedEvent.to_inferior +The gdb.Inferior (@pxref{Inferiors In Python}) that is now the current +inferior. +@end defvar + +@defvar InferiorChangedEvent.from_thread +The gdb.InferiorThread (@pxref{Threads In Python}) that used to be the +current thread. This can be @code{None} as an inferior that is not +yet running will have no threads, in which case, when switching from +such an inferior @code{from_thread} will be @code{None}. + +If this field is not @code{None} then it will represent a thread from +within the set @code{from_inferior.threads ()}. +@end defvar + +@defvar InferiorChangedEvent.to_thread +The gdb.InferiorThread (@pxref{Threads In Python}) that is now the +current thread. This can be @code{None} as an inferior that is not +yet running will have no threads, in which case, when switching to +such an inferior @code{to_thread} will be @code{None}. + +If this field is not @code{None} then it will represent a thread from +within the set @code{to_inferior.threads ()}. +@end defvar + @end table @node Threads In Python diff --git a/gdb/inferior.c b/gdb/inferior.c index 49f869a4c78..e16534c9b37 100644 --- a/gdb/inferior.c +++ b/gdb/inferior.c @@ -632,13 +632,12 @@ switch_to_inferior_no_thread (inferior *inf) static void inferior_command (const char *args, int from_tty) { - struct inferior *inf; - int num; + /* GDB always has a "current" inferior. */ + struct inferior *inf = current_inferior (); + gdb_assert (inf != nullptr); if (args == nullptr) { - inf = current_inferior (); - gdb_assert (inf != nullptr); const char *filename = inf->pspace->exec_filename.get (); if (filename == nullptr) @@ -650,7 +649,10 @@ inferior_command (const char *args, int from_tty) } else { - num = parse_and_eval_long (args); + struct inferior *old_inf = inf; + ptid_t old_inferior_ptid = inferior_ptid; + + int num = parse_and_eval_long (args); inf = find_inferior_id (num); if (inf == NULL) @@ -667,6 +669,9 @@ inferior_command (const char *args, int from_tty) switch_to_thread (tp); } + gdb::observers::inferior_changed.notify + (old_inf, old_inferior_ptid, inf, inferior_thread ()->ptid); + gdb::observers::user_selected_context_changed.notify (USER_SELECTED_INFERIOR | USER_SELECTED_THREAD @@ -676,6 +681,9 @@ inferior_command (const char *args, int from_tty) { switch_to_inferior_no_thread (inf); + gdb::observers::inferior_changed.notify + (old_inf, old_inferior_ptid, inf, null_ptid); + gdb::observers::user_selected_context_changed.notify (USER_SELECTED_INFERIOR); } diff --git a/gdb/observable.c b/gdb/observable.c index 10b8aad829e..c7b18e24341 100644 --- a/gdb/observable.c +++ b/gdb/observable.c @@ -77,6 +77,7 @@ DEFINE_OBSERVABLE (register_changed); DEFINE_OBSERVABLE (user_selected_context_changed); DEFINE_OBSERVABLE (source_styling_changed); DEFINE_OBSERVABLE (current_source_symtab_and_line_changed); +DEFINE_OBSERVABLE (inferior_changed); } /* namespace observers */ } /* namespace gdb */ diff --git a/gdb/observable.h b/gdb/observable.h index 915770ff363..021498b6387 100644 --- a/gdb/observable.h +++ b/gdb/observable.h @@ -251,6 +251,18 @@ extern observable<> source_styling_changed; extern observable<> current_source_symtab_and_line_changed; +/* This is notified when the user triggers a change in the current + inferior. This is notified before the user_selected_context_changed is + notified, this allows an observer to adjust the selected thread or + frame. FROM_INFERIOR and FROM_PTID identify the inferior and thread + being switched from, while TO_INFERIOR and TO_PTID are the inferior and + thread being switched too. The *_INFERIOR objects should never be + nullptr, however the *_PTID value might be null_ptid. */ +extern observable inferior_changed; + } /* namespace observers */ } /* namespace gdb */ diff --git a/gdb/python/lib/gdb/command/restore-thread.py b/gdb/python/lib/gdb/command/restore-thread.py new file mode 100644 index 00000000000..69b5556c322 --- /dev/null +++ b/gdb/python/lib/gdb/command/restore-thread.py @@ -0,0 +1,107 @@ +# Copyright (C) 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 . + +# Implement 'set/show restore-selected-thread' option. +# +# When the user switches between inferior restore the previously +# selected thread within the inferior being switched too. + +import gdb + +# A class that implements the 'set/show restore-selected-thread' +# option. An instance of this class can be treated as a boolean to +# indicate if this feature is on or off. +class restore_thread_parameter (gdb.Parameter): + '''When this option is on, GDB will record the currently selected thread for +each inferior, and restore the selected thread whenever GDB switches inferiors.''' + + set_doc = "Set whether GDB restores the selected thread when switching inferiors." + show_doc = "Show whether GDB restores the selected thread when switching inferiors." + + def __init__ (self): + gdb.Parameter.__init__ (self, "restore-selected-thread", + gdb.COMMAND_NONE, + gdb.PARAM_BOOLEAN) + self.value = False + + def get_show_string (self, value): + return ("Restoring previously selected thread is %s." + % (value)) + + def __nonzero__ (self): + if (self.value): + return 1 + else: + return 0 + + def __bool__ (self): + return self.value + +# Create an instance of the above class. This registers the option +# with the GDB core, but we also create a global object, this will +# allow us to check if the setting is on or off. +restore_selected_thread_p = restore_thread_parameter () + +# This map has keys that are inferior numbers as assigned by GDB, the +# values are the gdb.InferiorThread object that we last saw as +# selected for that inferior. +# +# We only update the values held in the map when we switch away from +# an inferior, trying keep the map up to date while a particular +# inferior is selected is not required. +inferior_to_thread_map = {} + +# Function called when the user changes the currently selected +# inferior. +def inferior_changed (event): + global restore_selected_thread_p + global inferior_to_thread_map + + # Record the previously selected thread. The thread object might + # be None, e.g. if the previous inferior was not running it will + # have had no threads. + # + # We always update this map even when restore-selected-thread is + # off, this means that if the user turns this setting on then they + # will start seeing the restore behaviour immediately. + inferior_to_thread_map[event.from_inferior.num] = event.from_thread + + # If restore-selected-thread is off then we're done. + if not restore_selected_thread_p: + return + + # If we have restore data for the inferior we are switching too, + # and the restore data is a gdb.InferiorThread, and the thread is + # still valid, then switch to that thread now. + # + # Otherwise, delete the map entry for the inferior we are + # switching too, the data we hold is clearly of no value. + if event.to_inferior.num in inferior_to_thread_map: + thr = inferior_to_thread_map[event.to_inferior.num] + if thr and thr.is_valid (): + thr.switch () + else: + del inferior_to_thread_map[event.to_inferior.num] + +# Function called when an inferior is deleted. Remove any restore +# data we are holding for that inferior. +def inferior_deleted (event): + global inferior_to_thread_map + if event.inferior.num in inferior_to_thread_map: + del inferior_to_thread_map[event.inferior.num] + +# Register the two even listeners. +gdb.events.inferior_changed.connect (inferior_changed) +gdb.events.inferior_deleted.connect (inferior_deleted) diff --git a/gdb/python/py-all-events.def b/gdb/python/py-all-events.def index d12a2103e80..22347d030a9 100644 --- a/gdb/python/py-all-events.def +++ b/gdb/python/py-all-events.def @@ -38,3 +38,4 @@ GDB_PY_DEFINE_EVENT(breakpoint_created) GDB_PY_DEFINE_EVENT(breakpoint_deleted) GDB_PY_DEFINE_EVENT(breakpoint_modified) GDB_PY_DEFINE_EVENT(before_prompt) +GDB_PY_DEFINE_EVENT(inferior_changed) diff --git a/gdb/python/py-event-types.def b/gdb/python/py-event-types.def index 70df4804fc4..12767e601b7 100644 --- a/gdb/python/py-event-types.def +++ b/gdb/python/py-event-types.def @@ -105,3 +105,8 @@ GDB_PY_DEFINE_EVENT_TYPE (thread, "ThreadEvent", "GDB thread event object", event_object_type); + +GDB_PY_DEFINE_EVENT_TYPE (inferior_changed, + "InferiorChangedEvent", + "GDB inferior changed event object", + event_object_type); diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c index a3d5952a10b..f3c535a3e65 100644 --- a/gdb/python/py-inferior.c +++ b/gdb/python/py-inferior.c @@ -296,6 +296,70 @@ python_inferior_deleted (struct inferior *inf) gdbpy_print_stack (); } +/* Called when the user switches between inferiors. Notify any Python + event listeners. */ +static void +python_inferior_changed (struct inferior *from_inferior, + ptid_t from_ptid, + struct inferior *to_inferior, + ptid_t to_ptid) +{ + if (!gdb_python_initialized) + return; + + gdbpy_enter enter_py (python_gdbarch, python_language); + + if (evregpy_no_listeners_p (gdb_py_events.inferior_changed)) + return; + + gdbpy_ref inf_from_obj + = inferior_to_inferior_object (from_inferior); + if (inf_from_obj == NULL) + { + gdbpy_print_stack (); + return; + } + + gdbpy_ref inf_to_obj + = inferior_to_inferior_object (to_inferior); + if (inf_to_obj == NULL) + { + gdbpy_print_stack (); + return; + } + + /* Helper to lookup a thread object given a ptid_t P. If no suitable + thread object is found (e.g. P is null_ptid), then a reference to None + is returned. */ + auto thread_from_ptid = [] (ptid_t p) -> gdbpy_ref<> + { + if (p == null_ptid) + return gdbpy_ref<>::new_reference (Py_None); + thread_info *thread + = find_thread_ptid (current_inferior ()->process_target (), + p); + if (thread != nullptr) + return thread_to_thread_object (thread); + return gdbpy_ref<>::new_reference (Py_None); + }; + + gdbpy_ref<> from_thr_obj = thread_from_ptid (from_ptid); + gdbpy_ref<> to_thr_obj = thread_from_ptid (from_ptid); + + gdbpy_ref<> event = create_event_object (&inferior_changed_event_object_type); + if (event == NULL + || evpy_add_attribute (event.get (), "from_inferior", + (PyObject *) inf_from_obj.get ()) < 0 + || evpy_add_attribute (event.get (), "to_inferior", + (PyObject *) inf_to_obj.get ()) < 0 + || evpy_add_attribute (event.get (), "from_thread", + (PyObject *) from_thr_obj.get ()) < 0 + || evpy_add_attribute (event.get (), "to_thread", + (PyObject *) to_thr_obj.get ()) < 0 + || evpy_emit_event (event.get (), gdb_py_events.inferior_changed) < 0) + gdbpy_print_stack (); +} + gdbpy_ref<> thread_to_thread_object (thread_info *thr) { @@ -916,6 +980,7 @@ gdbpy_initialize_inferior (void) gdb::observers::new_objfile.attach (python_new_objfile); gdb::observers::inferior_added.attach (python_new_inferior); gdb::observers::inferior_removed.attach (python_inferior_deleted); + gdb::observers::inferior_changed.attach (python_inferior_changed); membuf_object_type.tp_new = PyType_GenericNew; if (PyType_Ready (&membuf_object_type) < 0) diff --git a/gdb/testsuite/gdb.threads/restore-thread.c b/gdb/testsuite/gdb.threads/restore-thread.c new file mode 100644 index 00000000000..3eb1f722199 --- /dev/null +++ b/gdb/testsuite/gdb.threads/restore-thread.c @@ -0,0 +1,248 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2020 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 . */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* The number of threads to create. */ +volatile int thread_count = 3; + +/* This is initialised with our pid. GDB will read and print this value + from the Dejagnu test script, the test script will then use the pid to + send signals to this process. */ +pid_t global_pid; + +/* Holds one end of two different pipes. Things written to READ will not + appear on WRITE. */ +struct pipe_fds +{ + int read; + int write; +}; + +/* Information passed into each thread. */ +struct thread_info +{ + /* Just a numeric id for the thread. */ + int id; + + /* File handles with which the worker thread can communicate with the + master thread. */ + struct pipe_fds fds; +}; + +/* The control information held by the master thread, one of these for each + worker thread. */ +struct thread_ctrl +{ + /* The actual pthread handle, used to join the threads. */ + pthread_t thread; + + /* File handles with which the master thread can communicate with the + worker threads. */ + struct pipe_fds fds; + + /* The information that is passed into the worker thread. */ + struct thread_info info; +}; + +/* Wait for a single byte of the read file handle in FDS. */ +static void +wait_on_byte (struct pipe_fds *fds) +{ + ssize_t rtn; + char c; + + while ((rtn = read (fds->read, &c, 1)) != 1) + { + if (rtn != -1 || errno != EINTR) + abort (); + } +} + +/* Send a single byte to the write file handle in FDS. */ +static void +send_byte (struct pipe_fds *fds) +{ + ssize_t rtn; + char c = 'x'; + while ((rtn = write (fds->write, &c, 1)) != 1) + { + if (rtn != -1 || errno != EINTR) + abort (); + } +} + +/* Create a function used to mark a breakpoint location. */ +#define BREAKPOINT_FUNC(N) \ + static void \ + breakpt_ ## N () \ + { \ + printf ("Hit breakpt_" #N "\n"); \ + } + +BREAKPOINT_FUNC (0) /* breakpt_0 */ +BREAKPOINT_FUNC (1) /* breakpt_1 */ +BREAKPOINT_FUNC (2) /* breakpt_2 */ + +/* The worker thread entry point. */ +static void * +thread_worker (void *arg) +{ + struct thread_info *info = (struct thread_info *) arg; + int id = info->id; + + printf ("Thread %d created.\n", id); + breakpt_0 (); + + /* Let the main thread know that this thread is now running. */ + send_byte (&info->fds); + + /* The thread with id #2 is special, it waits here for a nudge from the + main thread. */ + if (id == 2) + { + wait_on_byte (&info->fds); + breakpt_2 (); + send_byte (&info->fds); + } + + /* Now wait for an incoming message indicating that the thread should + exit. */ + wait_on_byte (&info->fds); + printf ("In thread %d, exiting...\n", id); + return NULL; +} + +/* Initialise CTRL for thread ID, this includes setting up all of the pipe + file handles. */ +static void +thread_ctrl_init (struct thread_ctrl *ctrl, int id) +{ + int fds[2]; + + ctrl->info.id = id; + if (pipe (fds)) + abort (); + ctrl->info.fds.read = fds[0]; + ctrl->fds.write = fds[1]; + + if (pipe (fds)) + abort (); + ctrl->fds.read = fds[0]; + ctrl->info.fds.write = fds[1]; +} + +/* Wait for a SIGUSR1 to arrive. Assumes that SIGUSR1 is blocked on entry + to this function. */ +static void +wait_for_sigusr1 (void) +{ + int signo; + sigset_t set; + + sigemptyset (&set); + sigaddset (&set, SIGUSR1); + + /* Wait for a SIGUSR1. */ + if (sigwait (&set, &signo) != 0) + abort (); + if (signo != SIGUSR1) + abort (); +} + +/* Main program. */ +int +main () +{ + sigset_t set; + int i, max = thread_count; + + /* Set an alarm in case the testsuite crashes, don't leave the test + running forever. */ + alarm (300); + + struct thread_ctrl *info = malloc (sizeof (struct thread_ctrl) * max); + if (info == NULL) + abort (); + + /* Put the pid somewhere easy for GDB to read, also print it. */ + global_pid = getpid (); + printf ("pid = %lld\n", ((long long) global_pid)); + + /* Block SIGUSR1, all threads will inherit this sigmask. */ + sigemptyset (&set); + sigaddset (&set, SIGUSR1); + if (pthread_sigmask (SIG_BLOCK, &set, NULL)) + abort (); + + /* Create each thread. */ + for (i = 0; i < max; ++i) + { + struct thread_ctrl *thr = &info[i]; + thread_ctrl_init (thr, i + 1); + + if (pthread_create (&thr->thread, NULL, thread_worker, &thr->info) != 0) + abort (); + + /* Wait for an indication that the thread has started, and is ready + for action. */ + wait_on_byte (&thr->fds); + } + + printf ("All threads created.\n"); + + /* Give thread thread #1 a little nudge. */ + if (max >= 2) + { + send_byte (&info[1].fds); + wait_on_byte (&info[1].fds); + } + + breakpt_1 (); + + /* For each thread in turn wait for a SIGUSR1 to arrive, signal the + thread so that it will exit (by sending it a byte down its pipe), then + join the newly exited thread. */ + for (i = 0; i < max; ++i) + { + struct thread_ctrl *thr = &info[i]; + + wait_for_sigusr1 (); + + printf ("Telling thread %d to exit\n", thr->info.id); + send_byte (&thr->fds); + + if (pthread_join (thr->thread, NULL) != 0) + abort (); + + printf ("Thread %d exited\n", thr->info.id); + } + + free (info); + + /* Final wait before exiting. */ + wait_for_sigusr1 (); + + return 0; +} diff --git a/gdb/testsuite/gdb.threads/restore-thread.exp b/gdb/testsuite/gdb.threads/restore-thread.exp new file mode 100644 index 00000000000..f768b123c74 --- /dev/null +++ b/gdb/testsuite/gdb.threads/restore-thread.exp @@ -0,0 +1,219 @@ +# This testcase is part of GDB, the GNU debugger. +# +# Copyright 2020 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 . + +# Test GDB's ability to restore the selected thread when switching +# between inferiors, and check what happens when the selected thread +# of one inferior exits while we have a different inferior selected. + +standard_testfile + +if [prepare_for_testing "failed to prepare" $binfile $srcfile \ + {debug pthreads}] { + return -1 +} + +# Check that the current thread is THR in inferior INF. +proc check_current_thread { inf thr {testname ""} } { + if {${testname} == ""} { + set testname "check_current_thread ${inf} ${thr}" + } + + # As a final check, lets check the output for the 'thread' + # command. + gdb_test "thread" "Current thread is ${inf}.${thr} .*" \ + "current thread is ${inf}.${thr}: $testname" +} + +# Switch to inferior number INF, we expect that thread number THR +# within the inferior will be selected. +proc switch_to_inferior { inf thr {testname ""} } { + if {${testname} == ""} { + set testname "switch_to_inferior $inf $thr" + } + + gdb_test "inferior $inf" \ + "Switching to inferior ${inf} .*Switching to thread ${inf}.${thr} .*" \ + "$testname: select inferior ${inf}" + + check_current_thread $inf $thr "$testname: check current thread" +} + +# Switch to thread number THR. INF should be the number of the +# currently selected inferior and is used when checking the currently +# selected thread. +proc switch_to_thread { inf thr {testname ""} } { + if {${testname} == ""} { + set testname "switch_to_thread $inf $thr" + } + + gdb_test "thread ${thr}" \ + "Switching to thread ${inf}.${thr} .*" \ + "${testname}: select thread ${thr}" + check_current_thread $inf $thr \ + "${testname}: check current thread" +} + +# Continue the program in the background. +proc continue_in_bg { testname } { + global gdb_prompt + + gdb_test_multiple "continue&" $testname { + -re "Continuing\\.\r\n$gdb_prompt " { + pass $gdb_test_name + } + } +} + +# Send SIGUSR1 to PID, this will cause one of that processes threads +# to exit (assuming the process is currently running). +proc send_thread_exit_signal { pid } { + global decimal + + remote_exec target "kill -USR1 ${pid}" + gdb_test_multiple "" "wait for thread to exit" { + -re "Thread $decimal exited.*exited\\\].*" { + } + } +} + +# Start of test script. + +set pid_1 0 +set pid_2 0 + +if ![runto_main] { + return -1 +} + +# Restoring the selected thread is off by default. Switch it on now. +gdb_test_no_output "set restore-selected-thread on" + +gdb_breakpoint "breakpt_0" +gdb_breakpoint "breakpt_1" + +with_test_prefix "start inferior 1" { + gdb_continue_to_breakpoint "created thread 1.2" ".* breakpt_0 .*" + gdb_continue_to_breakpoint "created thread 1.3" ".* breakpt_0 .*" + gdb_continue_to_breakpoint "created thread 1.4" ".* breakpt_0 .*" + gdb_continue_to_breakpoint "all inferior 1 threads created" \ + ".* breakpt_1 .*" + gdb_test "info threads" ".*" + set pid_1 [get_valueof "/d" "global_pid" 0] +} + +# Start another inferior. +gdb_test "add-inferior" [multi_line \ + "\\\[New inferior 2\\\]" \ + "Added inferior 2 .*" ] \ + "add empty inferior 2" +gdb_test "inferior 2" "Switching to inferior 2.*" \ + "switch to inferior 2" +gdb_test "file ${binfile}" ".*" "load file in inferior 2" + +with_test_prefix "start inferior 2" { + gdb_breakpoint "breakpt_2" + gdb_run_cmd + gdb_test "" "hit Breakpoint .*" \ + "runto breakpoint in main" + gdb_continue_to_breakpoint "created thread 2.2" ".* breakpt_0 .*" + gdb_continue_to_breakpoint "created thread 2.3" ".* breakpt_0 .*" + gdb_continue_to_breakpoint "created thread 2.4" ".* breakpt_0 .*" + gdb_continue_to_breakpoint "all inferior 2 threads created" \ + ".* breakpt_2 .*" + gdb_test "info threads" ".*" + set pid_2 [get_valueof "/d" "global_pid" 0] +} + +gdb_assert {${pid_1} != 0} "read the pid for inferior 1" +gdb_assert {${pid_2} != 0} "read the pid for inferior 2" + +check_current_thread 2 3 "check initial thread is 2.3" +switch_to_inferior 1 1 "first switch to thread 1.1" +switch_to_inferior 2 3 +switch_to_thread 2 2 + +switch_to_inferior 1 1 "second switch to thread 1.1" +switch_to_thread 1 3 +switch_to_inferior 2 2 + +# Inferior 2 is special; it will have stopped at breakpt_2, in thread +# 2.3. To set this inferior up so that threads can exit we need to +# continue to breakpt_1. +gdb_continue_to_breakpoint "all inferior 2 threads created" \ + ".* breakpt_1 .*" + +with_test_prefix "inferior 2 ready" { + check_current_thread 2 1 + + switch_to_inferior 1 3 + switch_to_thread 1 2 + + continue_in_bg "continue inferior 1" + switch_to_inferior 2 1 + switch_to_thread 2 2 + continue_in_bg "continue inferior 2" +} + +# Cause thread 1.2 to exit. +send_thread_exit_signal ${pid_1} + +with_test_prefix "after 1.2 exited" { + # We should go back to 1.1 now as 1.2 has exited. + switch_to_inferior 1 1 + switch_to_thread 1 4 + + # Cause thread 2.2 to exit. + send_thread_exit_signal ${pid_2} +} + +with_test_prefix "after 2.2 exited" { + # We should go back to 2.1 now as 2.2 has exited. + switch_to_inferior 2 1 + + # Cause thread 1.3 to exit. + send_thread_exit_signal ${pid_1} +} + +with_test_prefix "after 1.3 exited" { + # We should still switch back to 1.4 as only 1.3 exited. + switch_to_inferior 1 4 + + # Cause thread 2.3 to exit. + send_thread_exit_signal ${pid_2} +} + +with_test_prefix "after 2.3 exited" { + # Switch back to 2.1, which should still be selected. + switch_to_inferior 2 1 + + # Cause thread 1.4 to exit. + send_thread_exit_signal ${pid_1} +} + +with_test_prefix "after 1.4 exited" { + # We should now switch back to 1.1 as 1.4 exited, and 1.1 is the + # only thread left now. + switch_to_inferior 1 1 + + # Cause thread 2.4 to exit. + send_thread_exit_signal ${pid_2} +} + +with_test_prefix "after 2.4 exited" { + # Switch back to 2.1, which should still be selected. + switch_to_inferior 2 1 +} -- 2.25.4