#include "ui-out.h"
#include "btrace.h"
#include "target/waitstatus.h"
+#include "target/target.h"
#include "cli/cli-utils.h"
#include "gdbsupport/refcounted-object.h"
#include "gdbsupport/common-gdbthread.h"
m_thread_fsm = std::move (fsm);
}
+ /* Record the thread options last set for this thread. */
+
+ void set_thread_options (gdb_thread_options thread_options);
+
+ /* Get the thread options last set for this thread. */
+
+ gdb_thread_options thread_options () const
+ {
+ return m_thread_options;
+ }
+
int current_line = 0;
struct symtab *current_symtab = NULL;
left to do for the thread's execution command after the target
stops. Several execution commands use it. */
std::unique_ptr<struct thread_fsm> m_thread_fsm;
+
+ /* The thread options as last set with a call to
+ set_thread_options. */
+ gdb_thread_options m_thread_options;
};
using thread_info_resumed_with_pending_wait_status_node
/* Return true if THREAD is doing a displaced step. */
static bool
-ATTRIBUTE_UNUSED
displaced_step_in_progress_thread (thread_info *thread)
{
gdb_assert (thread != nullptr);
return status;
}
+/* Maybe disable thread-{cloned,created,exited} event reporting after
+ a step-over (either in-line or displaced) finishes. */
+
+static void
+update_thread_events_after_step_over (thread_info *event_thread)
+{
+ if (target_supports_set_thread_options (0))
+ {
+ /* We can control per-thread options. Disable events for the
+ event thread. */
+ event_thread->set_thread_options (0);
+ }
+ else
+ {
+ /* We can only control the target-wide target_thread_events
+ setting. Disable it, but only if other threads don't need it
+ enabled. */
+ if (!displaced_step_in_progress_any_thread ())
+ target_thread_events (false);
+ }
+}
+
/* If we displaced stepped an instruction successfully, adjust registers and
memory to yield the same effect the instruction would have had if we had
executed it at its original address, and return
if (!displaced->in_progress ())
return DISPLACED_STEP_FINISH_STATUS_OK;
+ update_thread_events_after_step_over (event_thread);
+
gdb_assert (event_thread->inf->displaced_step_state.in_progress_count > 0);
event_thread->inf->displaced_step_state.in_progress_count--;
else
target_pass_signals (signal_pass);
+ /* Request that the target report thread-{created,cloned} events in
+ the following situations:
+
+ - If we are performing an in-line step-over-breakpoint, then we
+ will remove a breakpoint from the target and only run the
+ current thread. We don't want any new thread (spawned by the
+ step) to start running, as it might miss the breakpoint.
+
+ - If we are stepping over a breakpoint out of line (displaced
+ stepping) then we won't remove a breakpoint from the target,
+ but, if the step spawns a new clone thread, then we will need
+ to fixup the $pc address in the clone child too, so we need it
+ to start stopped.
+ */
+ if (step_over_info_valid_p ()
+ || displaced_step_in_progress_thread (tp))
+ {
+ gdb_thread_options options = GDB_THREAD_OPTION_CLONE;
+ if (target_supports_set_thread_options (options))
+ tp->set_thread_options (options);
+ else
+ target_thread_events (true);
+ }
+
+ /* If we're resuming more than one thread simultaneously, then any
+ thread other than the leader is being set to run free. Clear any
+ previous thread option for those threads. */
+ if (resume_ptid != inferior_ptid && target_supports_set_thread_options (0))
+ {
+ process_stratum_target *resume_target = tp->inf->process_target ();
+ for (thread_info *thr_iter : all_non_exited_threads (resume_target,
+ resume_ptid))
+ if (thr_iter != tp)
+ thr_iter->set_thread_options (0);
+ }
+
infrun_debug_printf ("resume_ptid=%s, step=%d, sig=%s",
resume_ptid.to_string ().c_str (),
step, gdb_signal_to_symbol_string (sig));
back an event. */
gdb_assert (ecs->event_thread->control.trap_expected);
+ update_thread_events_after_step_over (ecs->event_thread);
+
clear_step_over_info ();
}
/* Support for the QThreadEvents packet. */
PACKET_QThreadEvents,
+ /* Support for the QThreadOptions packet. */
+ PACKET_QThreadOptions,
+
/* Support for multi-process extensions. */
PACKET_multiprocess_feature,
this can go away. */
bool wait_forever_enabled_p = true;
+ /* The set of thread options the target reported it supports, via
+ qSupported. */
+ gdb_thread_options supported_thread_options = 0;
+
private:
/* Asynchronous signal handle registered as event loop source for
when we have pending events ready to be passed to the core. */
void detach (inferior *, int) override;
void disconnect (const char *, int) override;
+ void commit_requested_thread_options ();
+
void commit_resumed () override;
void resume (ptid_t, int, enum gdb_signal) override;
ptid_t wait (ptid_t, struct target_waitstatus *, target_wait_flags) override;
void thread_events (int) override;
+ bool supports_set_thread_options (gdb_thread_options) override;
+
int can_do_single_step () override;
void terminal_inferior () override;
void remote_packet_size (const protocol_feature *feature,
packet_support support, const char *value);
+ void remote_supported_thread_options (const protocol_feature *feature,
+ enum packet_support support,
+ const char *value);
void remote_serial_quit_handler ();
void
remote_target::remote_packet_size (const protocol_feature *feature,
- enum packet_support support, const char *value)
+ enum packet_support support,
+ const char *value)
{
struct remote_state *rs = get_remote_state ();
remote->remote_packet_size (feature, support, value);
}
+void
+remote_target::remote_supported_thread_options (const protocol_feature *feature,
+ enum packet_support support,
+ const char *value)
+{
+ struct remote_state *rs = get_remote_state ();
+
+ m_features.m_protocol_packets[feature->packet].support = support;
+
+ if (support != PACKET_ENABLE)
+ return;
+
+ if (value == nullptr || *value == '\0')
+ {
+ warning (_("Remote target reported \"%s\" without supported options."),
+ feature->name);
+ return;
+ }
+
+ ULONGEST options = 0;
+ const char *p = unpack_varlen_hex (value, &options);
+
+ if (*p != '\0')
+ {
+ warning (_("Remote target reported \"%s\" with "
+ "bad thread options: \"%s\"."),
+ feature->name, value);
+ return;
+ }
+
+ /* Record the set of supported options. */
+ rs->supported_thread_options = (gdb_thread_option) options;
+}
+
+static void
+remote_supported_thread_options (remote_target *remote,
+ const protocol_feature *feature,
+ enum packet_support support,
+ const char *value)
+{
+ remote->remote_supported_thread_options (feature, support, value);
+}
+
static const struct protocol_feature remote_protocol_features[] = {
{ "PacketSize", PACKET_DISABLE, remote_packet_size, -1 },
{ "qXfer:auxv:read", PACKET_DISABLE, remote_supported_packet,
PACKET_Qbtrace_conf_pt_size },
{ "vContSupported", PACKET_DISABLE, remote_supported_packet, PACKET_vContSupported },
{ "QThreadEvents", PACKET_DISABLE, remote_supported_packet, PACKET_QThreadEvents },
+ { "QThreadOptions", PACKET_DISABLE, remote_supported_thread_options,
+ PACKET_QThreadOptions },
{ "no-resumed", PACKET_DISABLE, remote_supported_packet, PACKET_no_resumed },
{ "memory-tagging", PACKET_DISABLE, remote_supported_packet,
PACKET_memory_tagging_feature },
!= AUTO_BOOLEAN_FALSE)
remote_query_supported_append (&q, "QThreadEvents+");
+ if (m_features.packet_set_cmd_state (PACKET_QThreadOptions)
+ != AUTO_BOOLEAN_FALSE)
+ remote_query_supported_append (&q, "QThreadOptions+");
+
if (m_features.packet_set_cmd_state (PACKET_no_resumed)
!= AUTO_BOOLEAN_FALSE)
remote_query_supported_append (&q, "no-resumed+");
return;
}
+ commit_requested_thread_options ();
+
/* In all-stop, we can't mark REMOTE_ASYNC_GET_PENDING_EVENTS_TOKEN
(explained in remote-notif.c:handle_notification) so
remote_notif_process is not called. We need find a place where
if (!target_is_non_stop_p () || ::execution_direction == EXEC_REVERSE)
return;
+ commit_requested_thread_options ();
+
/* Try to send wildcard actions ("vCont;c" or "vCont;c:pPID.-1")
instead of resuming all threads of each process individually.
However, if any thread of a process must remain halted, we can't
}
}
+/* Implementation of the supports_set_thread_options target
+ method. */
+
+bool
+remote_target::supports_set_thread_options (gdb_thread_options options)
+{
+ remote_state *rs = get_remote_state ();
+ return (m_features.packet_support (PACKET_QThreadOptions) == PACKET_ENABLE
+ && (rs->supported_thread_options & options) == options);
+}
+
+/* For coalescing reasons, actually sending the options to the target
+ happens at resume time, via this function. See target_resume for
+ all-stop, and target_commit_resumed for non-stop. */
+
+void
+remote_target::commit_requested_thread_options ()
+{
+ struct remote_state *rs = get_remote_state ();
+
+ if (m_features.packet_support (PACKET_QThreadOptions) != PACKET_ENABLE)
+ return;
+
+ char *p = rs->buf.data ();
+ char *endp = p + get_remote_packet_size ();
+
+ /* Clear options for all threads by default. Note that unlike
+ vCont, the rightmost options that match a thread apply, so we
+ don't have to worry about whether we can use wildcard ptids. */
+ strcpy (p, "QThreadOptions;0");
+ p += strlen (p);
+
+ /* Send the QThreadOptions packet stored in P. */
+ auto flush = [&] ()
+ {
+ *p++ = '\0';
+
+ putpkt (rs->buf);
+ getpkt (&rs->buf, 0);
+
+ switch (m_features.packet_ok (rs->buf, PACKET_QThreadOptions))
+ {
+ case PACKET_OK:
+ if (strcmp (rs->buf.data (), "OK") != 0)
+ error (_("Remote refused setting thread options: %s"), rs->buf.data ());
+ break;
+ case PACKET_ERROR:
+ error (_("Remote failure reply: %s"), rs->buf.data ());
+ case PACKET_UNKNOWN:
+ gdb_assert_not_reached ("PACKET_UNKNOWN");
+ break;
+ }
+ };
+
+ /* Prepare P for another QThreadOptions packet. */
+ auto restart = [&] ()
+ {
+ p = rs->buf.data ();
+ strcpy (p, "QThreadOptions");
+ p += strlen (p);
+ };
+
+ /* Now set non-zero options for threads that need them. We don't
+ bother with the case of all threads of a process wanting the same
+ non-zero options as that's not an expected scenario. */
+ for (thread_info *tp : all_non_exited_threads (this))
+ {
+ gdb_thread_options options = tp->thread_options ();
+
+ if (options == 0)
+ continue;
+
+ /* It might be possible to we have more threads with options
+ than can fit a single QThreadOptions packet. So build each
+ options/thread pair in this separate buffer to make sure it
+ fits. */
+ constexpr size_t max_options_size = 100;
+ char obuf[max_options_size];
+ char *obuf_p = obuf;
+ char *obuf_endp = obuf + max_options_size;
+
+ *obuf_p++ = ';';
+ obuf_p += xsnprintf (obuf_p, obuf_endp - obuf_p, "%s",
+ phex_nz (options, sizeof (options)));
+ if (tp->ptid != magic_null_ptid)
+ {
+ *obuf_p++ = ':';
+ obuf_p = write_ptid (obuf_p, obuf_endp, tp->ptid);
+ }
+
+ size_t osize = obuf_p - obuf;
+ if (osize > endp - p)
+ {
+ /* This new options/thread pair doesn't fit the packet
+ buffer. Send what we have already. */
+ flush ();
+ restart ();
+
+ /* Should now fit. */
+ gdb_assert (osize <= endp - p);
+ }
+
+ memcpy (p, obuf, osize);
+ p += osize;
+ }
+
+ flush ();
+}
+
static void
show_remote_cmd (const char *args, int from_tty)
{
add_packet_config_cmd (PACKET_QThreadEvents, "QThreadEvents", "thread-events",
0);
+ add_packet_config_cmd (PACKET_QThreadOptions, "QThreadOptions",
+ "thread-options", 0);
+
add_packet_config_cmd (PACKET_no_resumed, "N stop reply",
"no-resumed-stop-reply", 0);
target_debug_do_print (X.get ())
#define target_debug_print_target_waitkind(X) \
target_debug_do_print (pulongest (X))
+#define target_debug_print_gdb_thread_options(X) \
+ target_debug_do_print (to_string (X).c_str ())
static void
target_debug_print_target_waitstatus_p (struct target_waitstatus *status)
int async_wait_fd () override;
bool has_pending_events () override;
void thread_events (int arg0) override;
+ bool supports_set_thread_options (gdb_thread_options arg0) override;
bool supports_non_stop () override;
bool always_non_stop_p () override;
int find_memory_regions (find_memory_region_ftype arg0, void *arg1) override;
int async_wait_fd () override;
bool has_pending_events () override;
void thread_events (int arg0) override;
+ bool supports_set_thread_options (gdb_thread_options arg0) override;
bool supports_non_stop () override;
bool always_non_stop_p () override;
int find_memory_regions (find_memory_region_ftype arg0, void *arg1) override;
gdb_puts (")\n", gdb_stdlog);
}
+bool
+target_ops::supports_set_thread_options (gdb_thread_options arg0)
+{
+ return this->beneath ()->supports_set_thread_options (arg0);
+}
+
+bool
+dummy_target::supports_set_thread_options (gdb_thread_options arg0)
+{
+ return false;
+}
+
+bool
+debug_target::supports_set_thread_options (gdb_thread_options arg0)
+{
+ bool result;
+ gdb_printf (gdb_stdlog, "-> %s->supports_set_thread_options (...)\n", this->beneath ()->shortname ());
+ result = this->beneath ()->supports_set_thread_options (arg0);
+ gdb_printf (gdb_stdlog, "<- %s->supports_set_thread_options (", this->beneath ()->shortname ());
+ target_debug_print_gdb_thread_options (arg0);
+ gdb_puts (") = ", gdb_stdlog);
+ target_debug_print_bool (result);
+ gdb_puts ("\n", gdb_stdlog);
+ return result;
+}
+
bool
target_ops::supports_non_stop ()
{
current_inferior ()->top_target ()->thread_events (enable);
}
+/* See target.h. */
+
+bool
+target_supports_set_thread_options (gdb_thread_options options)
+{
+ inferior *inf = current_inferior ();
+ return inf->top_target ()->supports_set_thread_options (options);
+}
+
/* Controls if targets can report that they can/are async. This is
just for maintainers to use when debugging gdb. */
bool target_async_permitted = true;
TARGET_DEFAULT_RETURN (false);
virtual void thread_events (int)
TARGET_DEFAULT_IGNORE ();
+ /* Returns true if the target supports setting thread options
+ OPTIONS, false otherwise. */
+ virtual bool supports_set_thread_options (gdb_thread_options options)
+ TARGET_DEFAULT_RETURN (false);
/* This method must be implemented in some situations. See the
comment on 'can_run'. */
virtual bool supports_non_stop ()
/* Enables/disables thread create and exit events. */
extern void target_thread_events (int enable);
+/* Returns true if the target supports setting thread options
+ OPTIONS. */
+extern bool target_supports_set_thread_options (gdb_thread_options options);
+
/* Whether support for controlling the target backends always in
non-stop mode is enabled. */
extern enum auto_boolean target_non_stop_enabled;
return gdb::unique_xmalloc_ptr<char> ((char *) buffer.release ());
}
+
+/* See target/target.h. */
+
+std::string
+to_string (gdb_thread_options options)
+{
+ static constexpr gdb_thread_options::string_mapping mapping[] = {
+ MAP_ENUM_FLAG (GDB_THREAD_OPTION_CLONE),
+ };
+ return options.to_string (mapping);
+}
#include "target/waitstatus.h"
#include "target/wait.h"
+#include "gdbsupport/enum-flags.h"
/* This header is a stopgap until more code is shared. */
+/* Available thread options. Keep this in sync with to_string, in
+ target.c. */
+
+enum gdb_thread_option : unsigned
+{
+ /* Tell the target to report TARGET_WAITKIND_THREAD_CLONED events
+ for the thread. */
+ GDB_THREAD_OPTION_CLONE = 1 << 0,
+};
+
+DEF_ENUM_FLAGS_TYPE (enum gdb_thread_option, gdb_thread_options);
+
+/* Convert gdb_thread_option to a string. */
+extern std::string to_string (gdb_thread_options options);
+
/* Read LEN bytes of target memory at address MEMADDR, placing the
results in GDB's memory at MYADDR. Return zero for success,
nonzero if any error occurs. This function must be provided by
/* See gdbthread.h. */
+void
+thread_info::set_thread_options (gdb_thread_options thread_options)
+{
+ if (m_thread_options == thread_options)
+ return;
+
+ m_thread_options = thread_options;
+
+ infrun_debug_printf ("[options for %s are now %s]",
+ this->ptid.to_string ().c_str (),
+ to_string (thread_options).c_str ());
+}
+
+/* See gdbthread.h. */
+
int
thread_is_in_step_over_chain (struct thread_info *tp)
{
/* Branch trace target information for this thread. */
struct btrace_target_info *btrace = nullptr;
+
+ /* Thread options GDB requested with QThreadOptions. */
+ gdb_thread_options thread_options = 0;
};
extern std::list<thread_info *> all_threads;
#include "dll.h"
#include "hostio.h"
#include <vector>
+#include <unordered_map>
#include "gdbsupport/common-inferior.h"
#include "gdbsupport/job-control.h"
#include "gdbsupport/environ.h"
return true;
}
+/* Parse thread options starting at *P and return them. On exit,
+ advance *P past the options. */
+
+static gdb_thread_options
+parse_gdb_thread_options (const char **p)
+{
+ ULONGEST options = 0;
+ *p = unpack_varlen_hex (*p, &options);
+ return (gdb_thread_option) options;
+}
+
/* Handle all of the extended 'Q' packets. */
static void
return;
}
+ if (startswith (own_buf, "QThreadOptions;"))
+ {
+ const char *p = own_buf + strlen ("QThreadOptions");
+
+ gdb_thread_options supported_options = target_supported_thread_options ();
+ if (supported_options == 0)
+ {
+ /* Something went wrong -- we don't support any option, but
+ GDB sent the packet anyway. */
+ write_enn (own_buf);
+ return;
+ }
+
+ /* We could store the options directly in thread->thread_options
+ without this map, but that would mean that a QThreadOptions
+ packet with a wildcard like "QThreadOptions;0;3:TID" would
+ result in the debug logs showing:
+
+ [options for TID are now 0x0]
+ [options for TID are now 0x3]
+
+ It's nicer if we only print the final options for each TID,
+ and if we only print about it if the options changed compared
+ to the options that were previously set on the thread. */
+ std::unordered_map<thread_info *, gdb_thread_options> set_options;
+
+ while (*p != '\0')
+ {
+ if (p[0] != ';')
+ {
+ write_enn (own_buf);
+ return;
+ }
+ p++;
+
+ /* Read the options. */
+
+ gdb_thread_options options = parse_gdb_thread_options (&p);
+
+ if ((options & ~supported_options) != 0)
+ {
+ /* GDB asked for an unknown or unsupported option, so
+ error out. */
+ std::string err
+ = string_printf ("E.Unknown thread options requested: %s\n",
+ to_string (options).c_str ());
+ strcpy (own_buf, err.c_str ());
+ return;
+ }
+
+ ptid_t ptid;
+
+ if (p[0] == ';' || p[0] == '\0')
+ ptid = minus_one_ptid;
+ else if (p[0] == ':')
+ {
+ const char *q;
+
+ ptid = read_ptid (p + 1, &q);
+
+ if (p == q)
+ {
+ write_enn (own_buf);
+ return;
+ }
+ p = q;
+ if (p[0] != ';' && p[0] != '\0')
+ {
+ write_enn (own_buf);
+ return;
+ }
+ }
+ else
+ {
+ write_enn (own_buf);
+ return;
+ }
+
+ /* Convert PID.-1 => PID.0 for ptid.matches. */
+ if (ptid.lwp () == -1)
+ ptid = ptid_t (ptid.pid ());
+
+ for_each_thread ([&] (thread_info *thread)
+ {
+ if (ptid_of (thread).matches (ptid))
+ set_options[thread] = options;
+ });
+ }
+
+ for (const auto &iter : set_options)
+ {
+ thread_info *thread = iter.first;
+ gdb_thread_options options = iter.second;
+
+ if (thread->thread_options != options)
+ {
+ threads_debug_printf ("[options for %s are now %s]\n",
+ target_pid_to_str (ptid_of (thread)).c_str (),
+ to_string (options).c_str ());
+
+ thread->thread_options = options;
+ }
+ }
+
+ write_ok (own_buf);
+ return;
+ }
+
if (startswith (own_buf, "QStartupWithShell:"))
{
const char *value = own_buf + strlen ("QStartupWithShell:");
cs.vCont_supported = 1;
else if (feature == "QThreadEvents+")
;
+ else if (feature == "QThreadOptions+")
+ ;
else if (feature == "no-resumed+")
{
/* GDB supports and wants TARGET_WAITKIND_NO_RESUMED
strcat (own_buf, ";vContSupported+");
+ gdb_thread_options supported_options = target_supported_thread_options ();
+ if (supported_options != 0)
+ {
+ char *end_buf = own_buf + strlen (own_buf);
+ sprintf (end_buf, ";QThreadOptions=%s",
+ phex_nz (supported_options, sizeof (supported_options)));
+ }
+
strcat (own_buf, ";QThreadEvents+");
strcat (own_buf, ";no-resumed+");
return false;
}
+gdb_thread_options
+process_stratum_target::supported_thread_options ()
+{
+ return 0;
+}
+
bool
process_stratum_target::supports_exec_events ()
{
/* Returns true if vfork events are supported. */
virtual bool supports_vfork_events ();
+ /* Returns the set of supported thread options. */
+ virtual gdb_thread_options supported_thread_options ();
+
/* Returns true if exec events are supported. */
virtual bool supports_exec_events ();
#define target_supports_vfork_events() \
the_target->supports_vfork_events ()
+#define target_supported_thread_options(options) \
+ the_target->supported_thread_options (options)
+
#define target_supports_exec_events() \
the_target->supports_exec_events ()