mirror of
https://github.com/bminor/binutils-gdb.git
synced 2025-11-16 12:34:43 +00:00
Compare commits
30 Commits
1ae9fa5c60
...
users/palv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2321047a0 | ||
|
|
fb3a9c907f | ||
|
|
0f3b71f37e | ||
|
|
42d139787c | ||
|
|
7fde9d9131 | ||
|
|
09a31034a7 | ||
|
|
5e407d08c7 | ||
|
|
025e9d1b1b | ||
|
|
648e6605fb | ||
|
|
859163afc6 | ||
|
|
820ba3ec46 | ||
|
|
e0a33253d5 | ||
|
|
f2a97cd393 | ||
|
|
7482b6070d | ||
|
|
e0164bc389 | ||
|
|
bc0a080a33 | ||
|
|
f76edf890d | ||
|
|
ba8962fc09 | ||
|
|
7a2cd2ddcd | ||
|
|
39dab41284 | ||
|
|
cd8454230c | ||
|
|
6cff34433e | ||
|
|
a52b672f90 | ||
|
|
10cf06f133 | ||
|
|
a4afa97d40 | ||
|
|
1f0eb5b5f5 | ||
|
|
3d5fce9fb6 | ||
|
|
6bf0ade3d4 | ||
|
|
10f88baff2 | ||
|
|
0ccfd6822e |
27
gdb/NEWS
27
gdb/NEWS
@@ -81,6 +81,10 @@ show always-read-ctf
|
||||
When off, CTF is only read if DWARF is not present. When on, CTF is
|
||||
read regardless of whether DWARF is present. Off by default.
|
||||
|
||||
set remote thread-options-packet
|
||||
show remote thread-options-packet
|
||||
Set/show the use of the thread options packet.
|
||||
|
||||
* New convenience function "$_shell", to execute a shell command and
|
||||
return the result. This lets you run shell commands in expressions.
|
||||
Some examples:
|
||||
@@ -104,6 +108,22 @@ show always-read-ctf
|
||||
without a thread restriction. The same is also true for the 'task'
|
||||
field of an Ada task-specific breakpoint.
|
||||
|
||||
* New remote packets
|
||||
|
||||
New stop reason: clone
|
||||
Indicates that a clone system call was executed.
|
||||
|
||||
QThreadOptions
|
||||
Enable/disable optional event reporting, on a per-thread basis.
|
||||
Currently supported options are GDB_THREAD_OPTION_CLONE, to enable
|
||||
clone event reporting, and GDB_THREAD_OPTION_EXIT to enable thread
|
||||
exit event reporting.
|
||||
|
||||
QThreadOptions in qSupported
|
||||
The qSupported packet allows GDB to inform the stub it supports the
|
||||
QThreadOptions packet, and the qSupported response can contain the
|
||||
set of thread options the remote stub supports.
|
||||
|
||||
*** Changes in GDB 13
|
||||
|
||||
* MI version 1 is deprecated, and will be removed in GDB 14.
|
||||
@@ -118,6 +138,13 @@ show always-read-ctf
|
||||
from the current process state. GDB will show this additional information
|
||||
automatically, or through one of the memory-tag subcommands.
|
||||
|
||||
* Scheduler-locking and new threads
|
||||
|
||||
When scheduler-locking is in effect, only the current thread may run
|
||||
when the inferior is resumed. However, previously, new threads
|
||||
created by the resumed thread would still be able to run free. Now,
|
||||
they are held stopped.
|
||||
|
||||
* "info breakpoints" now displays enabled breakpoint locations of
|
||||
disabled breakpoints as in the "y-" state. For example:
|
||||
|
||||
|
||||
@@ -233,7 +233,9 @@ annotate_thread_changed (void)
|
||||
/* Emit notification on thread exit. */
|
||||
|
||||
static void
|
||||
annotate_thread_exited (struct thread_info *t, int silent)
|
||||
annotate_thread_exited (thread_info *t,
|
||||
gdb::optional<ULONGEST> exit_code,
|
||||
bool /* silent */)
|
||||
{
|
||||
if (annotation_level > 1)
|
||||
{
|
||||
|
||||
@@ -3243,7 +3243,9 @@ remove_breakpoints (void)
|
||||
that thread. */
|
||||
|
||||
static void
|
||||
remove_threaded_breakpoints (struct thread_info *tp, int silent)
|
||||
remove_threaded_breakpoints (thread_info *tp,
|
||||
gdb::optional<ULONGEST> exit_code,
|
||||
bool /* silent */)
|
||||
{
|
||||
for (breakpoint *b : all_breakpoints_safe ())
|
||||
{
|
||||
|
||||
@@ -192,12 +192,18 @@ write_memory_ptid (ptid_t ptid, CORE_ADDR memaddr,
|
||||
}
|
||||
|
||||
static bool
|
||||
displaced_step_instruction_executed_successfully (gdbarch *arch,
|
||||
gdb_signal signal)
|
||||
displaced_step_instruction_executed_successfully
|
||||
(gdbarch *arch, const target_waitstatus &status)
|
||||
{
|
||||
if (signal != GDB_SIGNAL_TRAP)
|
||||
if (status.kind () == TARGET_WAITKIND_STOPPED
|
||||
&& status.sig () != GDB_SIGNAL_TRAP)
|
||||
return false;
|
||||
|
||||
/* All other (thread event) waitkinds can only happen if the
|
||||
instruction fully executed. For example, a fork, or a syscall
|
||||
entry can only happen if the syscall instruction actually
|
||||
executed. */
|
||||
|
||||
if (target_stopped_by_watchpoint ())
|
||||
{
|
||||
if (gdbarch_have_nonsteppable_watchpoint (arch)
|
||||
@@ -210,7 +216,7 @@ displaced_step_instruction_executed_successfully (gdbarch *arch,
|
||||
|
||||
displaced_step_finish_status
|
||||
displaced_step_buffers::finish (gdbarch *arch, thread_info *thread,
|
||||
gdb_signal sig)
|
||||
const target_waitstatus &status)
|
||||
{
|
||||
gdb_assert (thread->displaced_step_state.in_progress ());
|
||||
|
||||
@@ -253,10 +259,17 @@ displaced_step_buffers::finish (gdbarch *arch, thread_info *thread,
|
||||
thread->ptid.to_string ().c_str (),
|
||||
paddress (arch, buffer->addr));
|
||||
|
||||
/* If the thread exited while stepping, we are done. The code above
|
||||
made the buffer available again, and we restored the bytes in the
|
||||
buffer. We don't want to run the fixup: since the thread is now
|
||||
dead there's nothing to adjust. */
|
||||
if (status.kind () == TARGET_WAITKIND_THREAD_EXITED)
|
||||
return DISPLACED_STEP_FINISH_STATUS_OK;
|
||||
|
||||
regcache *rc = get_thread_regcache (thread);
|
||||
|
||||
bool instruction_executed_successfully
|
||||
= displaced_step_instruction_executed_successfully (arch, sig);
|
||||
= displaced_step_instruction_executed_successfully (arch, status);
|
||||
|
||||
if (instruction_executed_successfully)
|
||||
{
|
||||
|
||||
@@ -168,7 +168,7 @@ struct displaced_step_buffers
|
||||
CORE_ADDR &displaced_pc);
|
||||
|
||||
displaced_step_finish_status finish (gdbarch *arch, thread_info *thread,
|
||||
gdb_signal sig);
|
||||
const target_waitstatus &status);
|
||||
|
||||
const displaced_step_copy_insn_closure *
|
||||
copy_insn_closure_by_addr (CORE_ADDR addr);
|
||||
|
||||
@@ -7050,7 +7050,9 @@ the following:
|
||||
There is no locking and any thread may run at any time.
|
||||
|
||||
@item on
|
||||
Only the current thread may run when the inferior is resumed.
|
||||
Only the current thread may run when the inferior is resumed. New
|
||||
threads created by the resumed thread are held stopped at their entry
|
||||
point, before they execute any instruction.
|
||||
|
||||
@item step
|
||||
Behaves like @code{on} when stepping, and @code{off} otherwise.
|
||||
@@ -24204,6 +24206,10 @@ future connections is shown. The available settings are:
|
||||
@tab @code{QThreadEvents}
|
||||
@tab Tracking thread lifetime.
|
||||
|
||||
@item @code{thread-options}
|
||||
@tab @code{QThreadOptions}
|
||||
@tab Set thread event reporting options.
|
||||
|
||||
@item @code{no-resumed-stop-reply}
|
||||
@tab @code{no resumed thread left stop reply}
|
||||
@tab Tracking thread lifetime.
|
||||
@@ -42629,6 +42635,17 @@ appropriate @samp{qSupported} feature (@pxref{qSupported}). The
|
||||
remote stub must also supply the appropriate @samp{qSupported} feature
|
||||
indicating support.
|
||||
|
||||
@cindex thread clone events, remote reply
|
||||
@anchor{thread clone event}
|
||||
@item clone
|
||||
The packet indicates that @code{clone} was called, and @var{r} is the
|
||||
thread ID of the new child thread, as specified in @ref{thread-id
|
||||
syntax}. This packet is only applicable to targets that support clone
|
||||
events.
|
||||
|
||||
This packet should not be sent by default; @value{GDBN} requests it
|
||||
with the @ref{QThreadOptions} packet.
|
||||
|
||||
@cindex thread create event, remote reply
|
||||
@anchor{thread create event}
|
||||
@item create
|
||||
@@ -42667,9 +42684,10 @@ hex strings.
|
||||
@item w @var{AA} ; @var{tid}
|
||||
|
||||
The thread exited, and @var{AA} is the exit status. This response
|
||||
should not be sent by default; @value{GDBN} requests it with the
|
||||
@ref{QThreadEvents} packet. See also @ref{thread create event} above.
|
||||
@var{AA} is formatted as a big-endian hex string.
|
||||
should not be sent by default; @value{GDBN} requests it with either
|
||||
the @ref{QThreadEvents} or @ref{QThreadOptions} packets. See also
|
||||
@ref{thread create event} above. @var{AA} is formatted as a
|
||||
big-endian hex string.
|
||||
|
||||
@item N
|
||||
There are no resumed threads left in the target. In other words, even
|
||||
@@ -43394,6 +43412,11 @@ same thread. @value{GDBN} does not enable this feature unless the
|
||||
stub reports that it supports it by including @samp{QThreadEvents+} in
|
||||
its @samp{qSupported} reply.
|
||||
|
||||
This packet always enables/disables event reporting for all threads of
|
||||
all processes under control of the remote stub. For per-thread
|
||||
control of optional event reporting, see the @ref{QThreadOptions}
|
||||
packet.
|
||||
|
||||
Reply:
|
||||
@table @samp
|
||||
@item OK
|
||||
@@ -43410,6 +43433,94 @@ the stub.
|
||||
Use of this packet is controlled by the @code{set remote thread-events}
|
||||
command (@pxref{Remote Configuration, set remote thread-events}).
|
||||
|
||||
@anchor{QThreadOptions}
|
||||
@item QThreadOptions@r{[};@var{options}@r{[}:@var{thread-id}@r{]]}@dots{}
|
||||
@cindex thread options, remote request
|
||||
@cindex @samp{QThreadOptions} packet
|
||||
|
||||
For each inferior thread, the last @var{options} in the list with a
|
||||
matching @var{thread-id} are applied. Any options previously set on a
|
||||
thread are discarded and replaced by the new options specified.
|
||||
Threads that do not match any @var{thread-id} retain their
|
||||
previously-set options. Thread IDs are specified using the syntax
|
||||
described in @ref{thread-id syntax}. If multiprocess extensions
|
||||
(@pxref{multiprocess extensions}) are supported, options can be
|
||||
specified to apply to all threads of a process by using the
|
||||
@samp{p@var{pid}.-1} form of @var{thread-id}. Options with no
|
||||
@var{thread-id} apply to all threads. Specifying no options is an
|
||||
error.
|
||||
|
||||
@var{options} is the bitwise @code{OR} of the following values. All
|
||||
values are given in hexadecimal representation.
|
||||
|
||||
@table @code
|
||||
@item GDB_THREAD_OPTION_CLONE (0x1)
|
||||
Report thread clone events (@pxref{thread clone event}). This is only
|
||||
meaningful for targets that support clone events (e.g., GNU/Linux
|
||||
systems).
|
||||
|
||||
@item GDB_THREAD_OPTION_EXIT (0x2)
|
||||
Report thread exit events (@pxref{thread exit event}).
|
||||
@end table
|
||||
|
||||
@noindent
|
||||
|
||||
For example, @value{GDBN} enables the @code{GDB_THREAD_OPTION_EXIT}
|
||||
and @code{GDB_THREAD_OPTION_CLONE} options when single-stepping a
|
||||
thread past a breakpoint, for the following reasons:
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
If the single-stepped thread exits (e.g., it executes a thread exit
|
||||
system call), enabling @code{GDB_THREAD_OPTION_EXIT} prevents
|
||||
@value{GDBN} from waiting forever, not knowing that it should no
|
||||
longer expect a stop for that same thread, and blocking other threads
|
||||
from progressing.
|
||||
|
||||
@item
|
||||
If the single-stepped thread spawns a new clone child (i.e., it
|
||||
executes a clone system call), enabling @code{GDB_THREAD_OPTION_CLONE}
|
||||
halts the cloned thread before it executes any instructions, and thus
|
||||
prevents the following problematic situations:
|
||||
|
||||
@itemize @minus
|
||||
@item
|
||||
If the breakpoint is stepped-over in-line, the spawned thread would
|
||||
incorrectly run free while the breakpoint being stepped over is not
|
||||
inserted, and thus the cloned thread may potentially run past the
|
||||
breakpoint without stopping for it;
|
||||
|
||||
@item
|
||||
If displaced (out-of-line) stepping is used, the cloned thread starts
|
||||
running at the out-of-line PC, leading to undefined behavior, usually
|
||||
crashing or corrupting data.
|
||||
@end itemize
|
||||
|
||||
@end itemize
|
||||
|
||||
New threads start with thread options cleared.
|
||||
|
||||
@value{GDBN} does not enable this feature unless the stub reports that
|
||||
it supports it by including
|
||||
@samp{QThreadOptions=@var{supported_options}} in its @samp{qSupported}
|
||||
reply.
|
||||
|
||||
Reply:
|
||||
@table @samp
|
||||
@item OK
|
||||
The request succeeded.
|
||||
|
||||
@item E @var{nn}
|
||||
An error occurred. The error number @var{nn} is given as hex digits.
|
||||
|
||||
@item @w{}
|
||||
An empty reply indicates that @samp{QThreadOptions} is not supported by
|
||||
the stub.
|
||||
@end table
|
||||
|
||||
Use of this packet is controlled by the @code{set remote thread-options}
|
||||
command (@pxref{Remote Configuration, set remote thread-options}).
|
||||
|
||||
@item qRcmd,@var{command}
|
||||
@cindex execute remote command, remote request
|
||||
@cindex @samp{qRcmd} packet
|
||||
@@ -43855,6 +43966,11 @@ These are the currently defined stub features and their properties:
|
||||
@tab @samp{-}
|
||||
@tab No
|
||||
|
||||
@item @samp{QThreadOptions}
|
||||
@tab Yes
|
||||
@tab @samp{-}
|
||||
@tab No
|
||||
|
||||
@item @samp{no-resumed}
|
||||
@tab No
|
||||
@tab @samp{-}
|
||||
@@ -44076,6 +44192,13 @@ The remote stub reports the supported actions in the reply to
|
||||
@item QThreadEvents
|
||||
The remote stub understands the @samp{QThreadEvents} packet.
|
||||
|
||||
@item QThreadOptions=@var{supported_options}
|
||||
The remote stub understands the @samp{QThreadOptions} packet.
|
||||
@var{supported_options} indicates the set of thread options the remote
|
||||
stub supports. @var{supported_options} has the same format as the
|
||||
@var{options} parameter of the @code{QThreadOptions} packet, described
|
||||
at @ref{QThreadOptions}.
|
||||
|
||||
@item no-resumed
|
||||
The remote stub reports the @samp{N} stop reply.
|
||||
|
||||
|
||||
@@ -1300,9 +1300,6 @@ fbsd_nat_target::wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus,
|
||||
{
|
||||
fbsd_lwp_debug_printf ("deleting thread for LWP %u",
|
||||
pl.pl_lwpid);
|
||||
if (print_thread_events)
|
||||
gdb_printf (_("[%s exited]\n"),
|
||||
target_pid_to_str (wptid).c_str ());
|
||||
low_delete_thread (thr);
|
||||
delete_thread (thr);
|
||||
}
|
||||
|
||||
@@ -1101,10 +1101,14 @@ typedef displaced_step_prepare_status (gdbarch_displaced_step_prepare_ftype) (st
|
||||
extern displaced_step_prepare_status gdbarch_displaced_step_prepare (struct gdbarch *gdbarch, thread_info *thread, CORE_ADDR &displaced_pc);
|
||||
extern void set_gdbarch_displaced_step_prepare (struct gdbarch *gdbarch, gdbarch_displaced_step_prepare_ftype *displaced_step_prepare);
|
||||
|
||||
/* Clean up after a displaced step of THREAD. */
|
||||
/* Clean up after a displaced step of THREAD.
|
||||
|
||||
typedef displaced_step_finish_status (gdbarch_displaced_step_finish_ftype) (struct gdbarch *gdbarch, thread_info *thread, gdb_signal sig);
|
||||
extern displaced_step_finish_status gdbarch_displaced_step_finish (struct gdbarch *gdbarch, thread_info *thread, gdb_signal sig);
|
||||
It is possible for the displaced-stepped instruction to have caused
|
||||
the thread to exit. The implementation can detect this case by
|
||||
checking if WS.kind is TARGET_WAITKIND_THREAD_EXITED. */
|
||||
|
||||
typedef displaced_step_finish_status (gdbarch_displaced_step_finish_ftype) (struct gdbarch *gdbarch, thread_info *thread, const target_waitstatus &ws);
|
||||
extern displaced_step_finish_status gdbarch_displaced_step_finish (struct gdbarch *gdbarch, thread_info *thread, const target_waitstatus &ws);
|
||||
extern void set_gdbarch_displaced_step_finish (struct gdbarch *gdbarch, gdbarch_displaced_step_finish_ftype *displaced_step_finish);
|
||||
|
||||
/* Return the closure associated to the displaced step buffer that is at ADDR. */
|
||||
|
||||
@@ -4096,13 +4096,13 @@ set_gdbarch_displaced_step_prepare (struct gdbarch *gdbarch,
|
||||
}
|
||||
|
||||
displaced_step_finish_status
|
||||
gdbarch_displaced_step_finish (struct gdbarch *gdbarch, thread_info *thread, gdb_signal sig)
|
||||
gdbarch_displaced_step_finish (struct gdbarch *gdbarch, thread_info *thread, const target_waitstatus &ws)
|
||||
{
|
||||
gdb_assert (gdbarch != NULL);
|
||||
gdb_assert (gdbarch->displaced_step_finish != NULL);
|
||||
if (gdbarch_debug >= 2)
|
||||
gdb_printf (gdb_stdlog, "gdbarch_displaced_step_finish called\n");
|
||||
return gdbarch->displaced_step_finish (gdbarch, thread, sig);
|
||||
return gdbarch->displaced_step_finish (gdbarch, thread, ws);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -1875,10 +1875,14 @@ Throw an exception if any unexpected error happens.
|
||||
Method(
|
||||
comment="""
|
||||
Clean up after a displaced step of THREAD.
|
||||
|
||||
It is possible for the displaced-stepped instruction to have caused
|
||||
the thread to exit. The implementation can detect this case by
|
||||
checking if WS.kind is TARGET_WAITKIND_THREAD_EXITED.
|
||||
""",
|
||||
type="displaced_step_finish_status",
|
||||
name="displaced_step_finish",
|
||||
params=[("thread_info *", "thread"), ("gdb_signal", "sig")],
|
||||
params=[("thread_info *", "thread"), ("const target_waitstatus &", "ws")],
|
||||
predefault="NULL",
|
||||
invalid="(! gdbarch->displaced_step_finish) != (! gdbarch->displaced_step_prepare)",
|
||||
)
|
||||
|
||||
@@ -28,6 +28,7 @@ struct symtab;
|
||||
#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"
|
||||
@@ -470,6 +471,17 @@ public:
|
||||
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;
|
||||
|
||||
@@ -577,6 +589,10 @@ private:
|
||||
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
|
||||
target_set_thread_options. */
|
||||
gdb_thread_options m_thread_options;
|
||||
};
|
||||
|
||||
using thread_info_resumed_with_pending_wait_status_node
|
||||
@@ -620,16 +636,30 @@ extern struct thread_info *add_thread_with_info (process_stratum_target *targ,
|
||||
|
||||
/* Delete thread THREAD and notify of thread exit. If the thread is
|
||||
currently not deletable, don't actually delete it but still tag it
|
||||
as exited and do the notification. */
|
||||
extern void delete_thread (struct thread_info *thread);
|
||||
as exited and do the notification. EXIT_CODE is the thread's exit
|
||||
code. If SILENT, don't actually notify the CLI. THREAD must not
|
||||
be NULL or an assertion will fail. */
|
||||
extern void delete_thread_with_exit_code (thread_info *thread,
|
||||
ULONGEST exit_code,
|
||||
bool silent = false);
|
||||
|
||||
/* Delete thread THREAD and notify of thread exit. If the thread is
|
||||
currently not deletable, don't actually delete it but still tag it
|
||||
as exited and do the notification. THREAD must not be NULL or an
|
||||
assertion will fail. */
|
||||
extern void delete_thread (thread_info *thread);
|
||||
|
||||
/* Like delete_thread, but be quiet about it. Used when the process
|
||||
this thread belonged to has already exited, for example. */
|
||||
extern void delete_thread_silent (struct thread_info *thread);
|
||||
|
||||
/* Mark the thread exited, but don't delete it or remove it from the
|
||||
inferior thread list. */
|
||||
extern void set_thread_exited (thread_info *tp, bool silent);
|
||||
inferior thread list. EXIT_CODE is the thread's exit code, if
|
||||
available. If SILENT, then don't inform the CLI about the
|
||||
exit. */
|
||||
extern void set_thread_exited (thread_info *tp,
|
||||
gdb::optional<ULONGEST> exit_code = {},
|
||||
bool silent = false);
|
||||
|
||||
/* Delete a step_resume_breakpoint from the thread database. */
|
||||
extern void delete_step_resume_breakpoint (struct thread_info *);
|
||||
|
||||
@@ -217,13 +217,13 @@ add_inferior (int pid)
|
||||
/* See inferior.h. */
|
||||
|
||||
void
|
||||
inferior::clear_thread_list (bool silent)
|
||||
inferior::clear_thread_list ()
|
||||
{
|
||||
thread_list.clear_and_dispose ([=] (thread_info *thr)
|
||||
{
|
||||
threads_debug_printf ("deleting thread %s, silent = %d",
|
||||
thr->ptid.to_string ().c_str (), silent);
|
||||
set_thread_exited (thr, silent);
|
||||
threads_debug_printf ("deleting thread %s",
|
||||
thr->ptid.to_string ().c_str ());
|
||||
set_thread_exited (thr, {}, true);
|
||||
if (thr->deletable ())
|
||||
delete thr;
|
||||
});
|
||||
@@ -233,7 +233,7 @@ inferior::clear_thread_list (bool silent)
|
||||
void
|
||||
delete_inferior (struct inferior *inf)
|
||||
{
|
||||
inf->clear_thread_list (true);
|
||||
inf->clear_thread_list ();
|
||||
|
||||
auto it = inferior_list.iterator_to (*inf);
|
||||
inferior_list.erase (it);
|
||||
@@ -259,7 +259,7 @@ delete_inferior (struct inferior *inf)
|
||||
static void
|
||||
exit_inferior_1 (struct inferior *inf, int silent)
|
||||
{
|
||||
inf->clear_thread_list (silent);
|
||||
inf->clear_thread_list ();
|
||||
|
||||
gdb::observers::inferior_exit.notify (inf);
|
||||
|
||||
|
||||
@@ -490,9 +490,8 @@ public:
|
||||
inline safe_inf_threads_range threads_safe ()
|
||||
{ return safe_inf_threads_range (this->thread_list.begin ()); }
|
||||
|
||||
/* Delete all threads in the thread list. If SILENT, exit threads
|
||||
silently. */
|
||||
void clear_thread_list (bool silent);
|
||||
/* Delete all threads in the thread list, silently. */
|
||||
void clear_thread_list ();
|
||||
|
||||
/* Continuations-related methods. A continuation is an std::function
|
||||
to be called to finish the execution of a command when running
|
||||
@@ -698,6 +697,8 @@ extern void detach_inferior (inferior *inf);
|
||||
|
||||
extern void exit_inferior (inferior *inf);
|
||||
|
||||
/* Like exit_inferior, but be quiet -- don't announce the exit of the
|
||||
inferior's threads to the CLI. */
|
||||
extern void exit_inferior_silent (inferior *inf);
|
||||
|
||||
extern void exit_inferior_num_silent (int num);
|
||||
|
||||
611
gdb/infrun.c
611
gdb/infrun.c
@@ -104,6 +104,8 @@ static bool start_step_over (void);
|
||||
|
||||
static bool step_over_info_valid_p (void);
|
||||
|
||||
static bool schedlock_applies (struct thread_info *tp);
|
||||
|
||||
/* Asynchronous signal handler registered as event loop source for
|
||||
when we have pending events ready to be passed to the core. */
|
||||
static struct async_event_handler *infrun_async_inferior_event_token;
|
||||
@@ -1885,6 +1887,53 @@ displaced_step_prepare (thread_info *thread)
|
||||
return status;
|
||||
}
|
||||
|
||||
/* True if any thread of TARGET that matches RESUME_PTID requires
|
||||
target_thread_events enabled. This assumes TARGET does not support
|
||||
target thread options. */
|
||||
|
||||
static bool
|
||||
any_thread_needs_target_thread_events (process_stratum_target *target,
|
||||
ptid_t resume_ptid)
|
||||
{
|
||||
for (thread_info *tp : all_non_exited_threads (target, resume_ptid))
|
||||
if (displaced_step_in_progress_thread (tp)
|
||||
|| schedlock_applies (tp)
|
||||
|| tp->thread_fsm () != nullptr)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 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,
|
||||
const target_waitstatus &event_status)
|
||||
{
|
||||
if (schedlock_applies (event_thread))
|
||||
{
|
||||
/* If scheduler-locking applies, continue reporting
|
||||
thread-created/thread-cloned events. */
|
||||
return;
|
||||
}
|
||||
else if (target_supports_set_thread_options (0))
|
||||
{
|
||||
/* We can control per-thread options. Disable events for the
|
||||
event thread, unless the thread is gone. */
|
||||
if (event_status.kind () != TARGET_WAITKIND_THREAD_EXITED)
|
||||
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 in the target
|
||||
don't need it enabled. */
|
||||
process_stratum_target *target = event_thread->inf->process_target ();
|
||||
if (!any_thread_needs_target_thread_events (target, minus_one_ptid))
|
||||
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
|
||||
@@ -1895,14 +1944,42 @@ displaced_step_prepare (thread_info *thread)
|
||||
DISPLACED_STEP_FINISH_STATUS_OK as well. */
|
||||
|
||||
static displaced_step_finish_status
|
||||
displaced_step_finish (thread_info *event_thread, enum gdb_signal signal)
|
||||
displaced_step_finish (thread_info *event_thread,
|
||||
const target_waitstatus &event_status)
|
||||
{
|
||||
/* Check whether the parent is displaced stepping. */
|
||||
struct regcache *regcache = get_thread_regcache (event_thread);
|
||||
struct gdbarch *gdbarch = regcache->arch ();
|
||||
inferior *parent_inf = event_thread->inf;
|
||||
|
||||
/* If this was a fork/vfork/clone, this event indicates that the
|
||||
displaced stepping of the syscall instruction has been done, so
|
||||
we perform cleanup for parent here. Also note that this
|
||||
operation also cleans up the child for vfork, because their pages
|
||||
are shared. */
|
||||
|
||||
/* If this is a fork (child gets its own address space copy) and
|
||||
some displaced step buffers were in use at the time of the fork,
|
||||
restore the displaced step buffer bytes in the child process.
|
||||
|
||||
Architectures which support displaced stepping and fork events
|
||||
must supply an implementation of
|
||||
gdbarch_displaced_step_restore_all_in_ptid. This is not enforced
|
||||
during gdbarch validation to support architectures which support
|
||||
displaced stepping but not forks. */
|
||||
if (event_status.kind () == TARGET_WAITKIND_FORKED
|
||||
&& gdbarch_supports_displaced_stepping (gdbarch))
|
||||
gdbarch_displaced_step_restore_all_in_ptid
|
||||
(gdbarch, parent_inf, event_status.child_ptid ());
|
||||
|
||||
displaced_step_thread_state *displaced = &event_thread->displaced_step_state;
|
||||
|
||||
/* Was this thread performing a displaced step? */
|
||||
if (!displaced->in_progress ())
|
||||
return DISPLACED_STEP_FINISH_STATUS_OK;
|
||||
|
||||
update_thread_events_after_step_over (event_thread, event_status);
|
||||
|
||||
gdb_assert (event_thread->inf->displaced_step_state.in_progress_count > 0);
|
||||
event_thread->inf->displaced_step_state.in_progress_count--;
|
||||
|
||||
@@ -1916,8 +1993,39 @@ displaced_step_finish (thread_info *event_thread, enum gdb_signal signal)
|
||||
|
||||
/* Do the fixup, and release the resources acquired to do the displaced
|
||||
step. */
|
||||
return gdbarch_displaced_step_finish (displaced->get_original_gdbarch (),
|
||||
event_thread, signal);
|
||||
displaced_step_finish_status status
|
||||
= gdbarch_displaced_step_finish (displaced->get_original_gdbarch (),
|
||||
event_thread, event_status);
|
||||
|
||||
if (event_status.kind () == TARGET_WAITKIND_FORKED
|
||||
|| event_status.kind () == TARGET_WAITKIND_VFORKED
|
||||
|| event_status.kind () == TARGET_WAITKIND_THREAD_CLONED)
|
||||
{
|
||||
/* Since the vfork/fork/clone syscall instruction was executed
|
||||
in the scratchpad, the child's PC is also within the
|
||||
scratchpad. Set the child's PC to the parent's PC value,
|
||||
which has already been fixed up. Note: we use the parent's
|
||||
aspace here, although we're touching the child, because the
|
||||
child hasn't been added to the inferior list yet at this
|
||||
point. */
|
||||
|
||||
struct regcache *child_regcache
|
||||
= get_thread_arch_aspace_regcache (parent_inf->process_target (),
|
||||
event_status.child_ptid (),
|
||||
gdbarch,
|
||||
parent_inf->aspace);
|
||||
/* Read PC value of parent. */
|
||||
CORE_ADDR parent_pc = regcache_read_pc (regcache);
|
||||
|
||||
displaced_debug_printf ("write child pc from %s to %s",
|
||||
paddress (gdbarch,
|
||||
regcache_read_pc (child_regcache)),
|
||||
paddress (gdbarch, parent_pc));
|
||||
|
||||
regcache_write_pc (child_regcache, parent_pc);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Data to be passed around while handling an event. This data is
|
||||
@@ -2365,6 +2473,72 @@ do_target_resume (ptid_t resume_ptid, bool step, enum gdb_signal sig)
|
||||
else
|
||||
target_pass_signals (signal_pass);
|
||||
|
||||
/* Request that the target report thread-{created,cloned,exited}
|
||||
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. We
|
||||
need to clear the step-over state if the stepped thread exits,
|
||||
so we also enable thread-exit events.
|
||||
|
||||
- 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. We need to release the displaced stepping
|
||||
buffer if the stepped thread exits, so we also enable
|
||||
thread-exit events.
|
||||
|
||||
- If scheduler-locking applies, threads that the current thread
|
||||
spawns should remain halted. It's not strictly necessary to
|
||||
enable thread-exit events in this case, but it doesn't hurt.
|
||||
*/
|
||||
if (step_over_info_valid_p ()
|
||||
|| displaced_step_in_progress_thread (tp)
|
||||
|| schedlock_applies (tp))
|
||||
{
|
||||
gdb_thread_options options
|
||||
= GDB_THREAD_OPTION_CLONE | GDB_THREAD_OPTION_EXIT;
|
||||
if (target_supports_set_thread_options (options))
|
||||
tp->set_thread_options (options);
|
||||
else
|
||||
target_thread_events (true);
|
||||
}
|
||||
else if (tp->thread_fsm () != nullptr)
|
||||
{
|
||||
gdb_thread_options options = GDB_THREAD_OPTION_EXIT;
|
||||
if (target_supports_set_thread_options (options))
|
||||
tp->set_thread_options (options);
|
||||
else
|
||||
target_thread_events (true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (target_supports_set_thread_options (0))
|
||||
tp->set_thread_options (0);
|
||||
else
|
||||
{
|
||||
process_stratum_target *resume_target = tp->inf->process_target ();
|
||||
if (!any_thread_needs_target_thread_events (resume_target,
|
||||
resume_ptid))
|
||||
target_thread_events (false);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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));
|
||||
@@ -2452,7 +2626,28 @@ resume_1 (enum gdb_signal sig)
|
||||
step = false;
|
||||
}
|
||||
|
||||
CORE_ADDR pc = regcache_read_pc (regcache);
|
||||
CORE_ADDR pc = 0;
|
||||
try
|
||||
{
|
||||
pc = regcache_read_pc (regcache);
|
||||
}
|
||||
catch (const gdb_exception_error &err)
|
||||
{
|
||||
/* Swallow errors as it may be that the current thread exited
|
||||
and we've haven't seen its exit status yet. Let the
|
||||
resumption continue and we'll collect the exit event
|
||||
shortly. */
|
||||
if (err.error == TARGET_CLOSE_ERROR)
|
||||
throw;
|
||||
|
||||
if (debug_infrun)
|
||||
{
|
||||
string_file buf;
|
||||
exception_print (&buf, err);
|
||||
infrun_debug_printf ("resume: swallowing error: %s",
|
||||
buf.string ().c_str ());
|
||||
}
|
||||
}
|
||||
|
||||
infrun_debug_printf ("step=%d, signal=%s, trap_expected=%d, "
|
||||
"current thread [%s] at %s",
|
||||
@@ -3933,6 +4128,7 @@ struct wait_one_event
|
||||
};
|
||||
|
||||
static bool handle_one (const wait_one_event &event);
|
||||
static int finish_step_over (struct execution_control_state *ecs);
|
||||
|
||||
/* Prepare and stabilize the inferior for detaching it. E.g.,
|
||||
detaching while a thread is displaced stepping is a recipe for
|
||||
@@ -4122,7 +4318,12 @@ reinstall_readline_callback_handler_cleanup ()
|
||||
}
|
||||
|
||||
/* Clean up the FSMs of threads that are now stopped. In non-stop,
|
||||
that's just the event thread. In all-stop, that's all threads. */
|
||||
that's just the event thread. In all-stop, that's all threads. In
|
||||
all-stop, threads that had a pending exit no longer have a reason
|
||||
to be around, as their FSMs/commands are canceled, so we delete
|
||||
them. This avoids "info threads" listing such threads as if they
|
||||
were alive (and failing to read their registers), the user being to
|
||||
select and resume them (and that failing), etc. */
|
||||
|
||||
static void
|
||||
clean_up_just_stopped_threads_fsms (struct execution_control_state *ecs)
|
||||
@@ -4140,15 +4341,28 @@ clean_up_just_stopped_threads_fsms (struct execution_control_state *ecs)
|
||||
{
|
||||
scoped_restore_current_thread restore_thread;
|
||||
|
||||
for (thread_info *thr : all_non_exited_threads ())
|
||||
for (thread_info *thr : all_threads_safe ())
|
||||
{
|
||||
if (thr->thread_fsm () == nullptr)
|
||||
if (thr->state == THREAD_EXITED)
|
||||
continue;
|
||||
|
||||
if (thr == ecs->event_thread)
|
||||
continue;
|
||||
|
||||
switch_to_thread (thr);
|
||||
thr->thread_fsm ()->clean_up (thr);
|
||||
if (thr->thread_fsm () != nullptr)
|
||||
{
|
||||
switch_to_thread (thr);
|
||||
thr->thread_fsm ()->clean_up (thr);
|
||||
}
|
||||
|
||||
/* As we are cancelling the command/FSM of this thread,
|
||||
whatever was the reason we needed to report a thread
|
||||
exited event to the user, that reason is gone. Delete
|
||||
the thread, so that the user doesn't see it in the thread
|
||||
list, the next proceed doesn't try to resume it, etc. */
|
||||
if (thr->has_pending_waitstatus ()
|
||||
&& thr->pending_waitstatus ().kind () == TARGET_WAITKIND_THREAD_EXITED)
|
||||
delete_thread (thr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4922,6 +5136,8 @@ wait_one ()
|
||||
if (nfds == 0)
|
||||
{
|
||||
/* No waitable targets left. All must be stopped. */
|
||||
infrun_debug_printf ("no waitable targets left");
|
||||
|
||||
target_waitstatus ws;
|
||||
ws.set_no_resumed ();
|
||||
return {nullptr, minus_one_ptid, std::move (ws)};
|
||||
@@ -5095,6 +5311,16 @@ handle_one (const wait_one_event &event)
|
||||
event.ws);
|
||||
save_waitstatus (t, event.ws);
|
||||
t->stop_requested = false;
|
||||
|
||||
if (event.ws.kind () == TARGET_WAITKIND_THREAD_EXITED)
|
||||
{
|
||||
if (displaced_step_finish (t, event.ws)
|
||||
!= DISPLACED_STEP_FINISH_STATUS_OK)
|
||||
{
|
||||
gdb_assert_not_reached ("displaced_step_finish on "
|
||||
"exited thread failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -5122,7 +5348,7 @@ handle_one (const wait_one_event &event)
|
||||
/* We caught the event that we intended to catch, so
|
||||
there's no event to save as pending. */
|
||||
|
||||
if (displaced_step_finish (t, GDB_SIGNAL_0)
|
||||
if (displaced_step_finish (t, event.ws)
|
||||
== DISPLACED_STEP_FINISH_STATUS_NOT_EXECUTED)
|
||||
{
|
||||
/* Add it back to the step-over queue. */
|
||||
@@ -5137,7 +5363,6 @@ handle_one (const wait_one_event &event)
|
||||
}
|
||||
else
|
||||
{
|
||||
enum gdb_signal sig;
|
||||
struct regcache *regcache;
|
||||
|
||||
infrun_debug_printf
|
||||
@@ -5148,10 +5373,7 @@ handle_one (const wait_one_event &event)
|
||||
/* Record for later. */
|
||||
save_waitstatus (t, event.ws);
|
||||
|
||||
sig = (event.ws.kind () == TARGET_WAITKIND_STOPPED
|
||||
? event.ws.sig () : GDB_SIGNAL_0);
|
||||
|
||||
if (displaced_step_finish (t, sig)
|
||||
if (displaced_step_finish (t, event.ws)
|
||||
== DISPLACED_STEP_FINISH_STATUS_NOT_EXECUTED)
|
||||
{
|
||||
/* Add it back to the step-over queue. */
|
||||
@@ -5174,6 +5396,83 @@ handle_one (const wait_one_event &event)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Helper for stop_all_threads. wait_one waits for events until it
|
||||
sees a TARGET_WAITKIND_NO_RESUMED event. When it sees one, it
|
||||
disables target_async for the target to stop waiting for events
|
||||
from it. TARGET_WAITKIND_NO_RESUMED can be delayed though,
|
||||
consider, debugging against gdbserver:
|
||||
|
||||
#1 - Threads 1-5 are running, and thread 1 hits a breakpoint.
|
||||
|
||||
#2 - gdb processes the breakpoint hit for thread 1, stops all
|
||||
threads, and steps thread 1 over the breakpoint. while
|
||||
stopping threads, some other threads reported interesting
|
||||
events, which were left pending in the thread's objects
|
||||
(infrun's queue).
|
||||
|
||||
#2 - Thread 1 exits (it stepped an exit syscall), and gdbserver
|
||||
reports the thread exit for thread 1. The event ends up in
|
||||
remote's stop reply queue.
|
||||
|
||||
#3 - That was the last resumed thread, so gdbserver reports
|
||||
no-resumed, and that event also ends up in remote's stop
|
||||
reply queue, queued after the thread exit from #2.
|
||||
|
||||
#4 - gdb processes the thread exit event, which finishes the
|
||||
step-over, and so gdb restarts all threads (threads with
|
||||
pending events are left marked resumed, but aren't set
|
||||
executing). The no-resumed event is still left pending in
|
||||
the remote stop reply queue.
|
||||
|
||||
#5 - Since there are now resumed threads with pending breakpoint
|
||||
hits, gdb picks one at random to process next.
|
||||
|
||||
#5 - gdb picks the breakpoint hit for thread 2 this time, and that
|
||||
breakpoint also needs to be stepped over, so gdb stops all
|
||||
threads again.
|
||||
|
||||
#6 - stop_all_threads counts number of expected stops and calls
|
||||
wait_one once for each.
|
||||
|
||||
#7 - The first wait_one call collects the no-resumed event from #3
|
||||
above.
|
||||
|
||||
#9 - Seeing the no-resumed event, wait_one disables target async
|
||||
for the remote target, to stop waiting for events from it.
|
||||
wait_one from here on always return no-resumed directly
|
||||
without reaching the target.
|
||||
|
||||
#10 - stop_all_threads still hasn't seen all the stops it expects,
|
||||
so it does another pass.
|
||||
|
||||
#11 - Since the remote target is not async (disabled in #9),
|
||||
wait_one doesn't wait on it, so it won't see the expected
|
||||
stops, and instead returns no-resumed directly.
|
||||
|
||||
#12 - stop_all_threads still haven't seen all the stops, so it
|
||||
does another pass. goto #b, looping forever.
|
||||
|
||||
To handle this, we explicitly (re-)enable target async on all
|
||||
targets that can async every time stop_all_threads goes wait for
|
||||
the expected stops. */
|
||||
|
||||
static void
|
||||
reenable_target_async ()
|
||||
{
|
||||
for (inferior *inf : all_inferiors ())
|
||||
{
|
||||
process_stratum_target *target = inf->process_target ();
|
||||
if (target != nullptr
|
||||
&& target->threads_executing
|
||||
&& target->can_async_p ()
|
||||
&& !target->is_async_p ())
|
||||
{
|
||||
switch_to_inferior_no_thread (inf);
|
||||
target_async (1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* See infrun.h. */
|
||||
|
||||
void
|
||||
@@ -5300,6 +5599,8 @@ stop_all_threads (const char *reason, inferior *inf)
|
||||
if (pass > 0)
|
||||
pass = -1;
|
||||
|
||||
reenable_target_async ();
|
||||
|
||||
for (int i = 0; i < waits_needed; i++)
|
||||
{
|
||||
wait_one_event event = wait_one ();
|
||||
@@ -5310,7 +5611,9 @@ stop_all_threads (const char *reason, inferior *inf)
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle a TARGET_WAITKIND_NO_RESUMED event. */
|
||||
/* Handle a TARGET_WAITKIND_NO_RESUMED event. Return true if we
|
||||
handled the event and should continue waiting. Return false if we
|
||||
should stop and report the event to the user. */
|
||||
|
||||
static bool
|
||||
handle_no_resumed (struct execution_control_state *ecs)
|
||||
@@ -5438,6 +5741,139 @@ handle_no_resumed (struct execution_control_state *ecs)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Handle a TARGET_WAITKIND_THREAD_EXITED event. Return true if we
|
||||
handled the event and should continue waiting. Return false if we
|
||||
should stop and report the event to the user. */
|
||||
|
||||
static bool
|
||||
handle_thread_exited (execution_control_state *ecs)
|
||||
{
|
||||
context_switch (ecs);
|
||||
|
||||
/* Clear these so we don't re-start the thread stepping over a
|
||||
breakpoint/watchpoint. */
|
||||
ecs->event_thread->stepping_over_breakpoint = 0;
|
||||
ecs->event_thread->stepping_over_watchpoint = 0;
|
||||
|
||||
/* If the thread had an FSM, then abort the command. But only after
|
||||
finishing the step over, as in non-stop mode, aborting this
|
||||
thread's command should not interfere with other threads. We
|
||||
must check this before finish_step over, however, which may
|
||||
update the thread list and delete the event thread. */
|
||||
bool abort_cmd = (ecs->event_thread->thread_fsm () != nullptr);
|
||||
|
||||
/* Maybe the thread was doing a step-over, if so release
|
||||
resources and start any further pending step-overs.
|
||||
|
||||
If we are on a non-stop target and the thread was doing an
|
||||
in-line step, this also restarts the other threads. */
|
||||
int ret = finish_step_over (ecs);
|
||||
|
||||
/* finish_step_over returns true if it moves ecs' wait status
|
||||
back into the thread, so that we go handle another pending
|
||||
event before this one. But we know it never does that if
|
||||
the event thread has exited. */
|
||||
gdb_assert (ret == 0);
|
||||
|
||||
if (abort_cmd)
|
||||
{
|
||||
delete_thread (ecs->event_thread);
|
||||
ecs->event_thread = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If finish_step_over started a new in-line step-over, don't
|
||||
try to restart anything else. */
|
||||
if (step_over_info_valid_p ())
|
||||
{
|
||||
delete_thread (ecs->event_thread);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Maybe we are on an all-stop target and we got this event
|
||||
while doing a step-like command on another thread. If so,
|
||||
go back to doing that. If this thread was stepping,
|
||||
switch_back_to_stepped_thread will consider that the thread
|
||||
was interrupted mid-step and will try keep stepping it. We
|
||||
don't want that, the thread is gone. So clear the proceed
|
||||
status so it doesn't do that. */
|
||||
clear_proceed_status_thread (ecs->event_thread);
|
||||
if (switch_back_to_stepped_thread (ecs))
|
||||
{
|
||||
delete_thread (ecs->event_thread);
|
||||
return true;
|
||||
}
|
||||
|
||||
inferior *inf = ecs->event_thread->inf;
|
||||
bool slock_applies = schedlock_applies (ecs->event_thread);
|
||||
|
||||
delete_thread (ecs->event_thread);
|
||||
ecs->event_thread = nullptr;
|
||||
|
||||
/* Continue handling the event as if we had gotten a
|
||||
TARGET_WAITKIND_NO_RESUMED. */
|
||||
auto handle_as_no_resumed = [ecs] ()
|
||||
{
|
||||
/* handle_no_resumed doesn't really look at the event kind, but
|
||||
normal_stop does. */
|
||||
ecs->ws.set_no_resumed ();
|
||||
ecs->event_thread = nullptr;
|
||||
ecs->ptid = minus_one_ptid;
|
||||
|
||||
/* Re-record the last target status. */
|
||||
set_last_target_status (ecs->target, ecs->ptid, ecs->ws);
|
||||
|
||||
return handle_no_resumed (ecs);
|
||||
};
|
||||
|
||||
/* If we are on an all-stop target, the target has stopped all
|
||||
threads to report the event. We don't actually want to
|
||||
stop, so restart the threads. */
|
||||
if (!target_is_non_stop_p ())
|
||||
{
|
||||
if (slock_applies)
|
||||
{
|
||||
/* Since the target is !non-stop, then everything is stopped
|
||||
at this point, and we can't assume we'll get further
|
||||
events until we resume the target again. Handle this
|
||||
event like if it were a TARGET_WAITKIND_NO_RESUMED. Note
|
||||
this refreshes the thread list and checks whether there
|
||||
are other resumed threads before deciding whether to
|
||||
print "no-unwaited-for left". This is important because
|
||||
the user could have done:
|
||||
|
||||
(gdb) set scheduler-locking on
|
||||
(gdb) thread 1
|
||||
(gdb) c&
|
||||
(gdb) thread 2
|
||||
(gdb) c
|
||||
|
||||
... and only one of the threads exited. */
|
||||
return handle_as_no_resumed ();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Switch to the first non-exited thread we can find, and
|
||||
resume. */
|
||||
auto range = inf->non_exited_threads ();
|
||||
if (range.begin () == range.end ())
|
||||
{
|
||||
/* Looks like the target reported a
|
||||
TARGET_WAITKIND_THREAD_EXITED for its last known
|
||||
thread. */
|
||||
return handle_as_no_resumed ();
|
||||
}
|
||||
thread_info *non_exited_thread = *range.begin ();
|
||||
switch_to_thread (non_exited_thread);
|
||||
insert_breakpoints ();
|
||||
resume (GDB_SIGNAL_0);
|
||||
}
|
||||
}
|
||||
|
||||
prepare_to_wait (ecs);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Given an execution control state that has been freshly filled in by
|
||||
an event from the inferior, figure out what it means and take
|
||||
appropriate action.
|
||||
@@ -5476,12 +5912,6 @@ handle_inferior_event (struct execution_control_state *ecs)
|
||||
return;
|
||||
}
|
||||
|
||||
if (ecs->ws.kind () == TARGET_WAITKIND_THREAD_EXITED)
|
||||
{
|
||||
prepare_to_wait (ecs);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ecs->ws.kind () == TARGET_WAITKIND_NO_RESUMED
|
||||
&& handle_no_resumed (ecs))
|
||||
return;
|
||||
@@ -5496,7 +5926,6 @@ handle_inferior_event (struct execution_control_state *ecs)
|
||||
{
|
||||
/* No unwaited-for children left. IOW, all resumed children
|
||||
have exited. */
|
||||
stop_print_frame = false;
|
||||
stop_waiting (ecs);
|
||||
return;
|
||||
}
|
||||
@@ -5645,6 +6074,12 @@ handle_inferior_event (struct execution_control_state *ecs)
|
||||
keep_going (ecs);
|
||||
return;
|
||||
|
||||
case TARGET_WAITKIND_THREAD_EXITED:
|
||||
if (handle_thread_exited (ecs))
|
||||
return;
|
||||
stop_waiting (ecs);
|
||||
break;
|
||||
|
||||
case TARGET_WAITKIND_EXITED:
|
||||
case TARGET_WAITKIND_SIGNALLED:
|
||||
{
|
||||
@@ -5720,67 +6155,13 @@ handle_inferior_event (struct execution_control_state *ecs)
|
||||
|
||||
case TARGET_WAITKIND_FORKED:
|
||||
case TARGET_WAITKIND_VFORKED:
|
||||
/* Check whether the inferior is displaced stepping. */
|
||||
{
|
||||
struct regcache *regcache = get_thread_regcache (ecs->event_thread);
|
||||
struct gdbarch *gdbarch = regcache->arch ();
|
||||
inferior *parent_inf = find_inferior_ptid (ecs->target, ecs->ptid);
|
||||
case TARGET_WAITKIND_THREAD_CLONED:
|
||||
|
||||
/* If this is a fork (child gets its own address space copy)
|
||||
and some displaced step buffers were in use at the time of
|
||||
the fork, restore the displaced step buffer bytes in the
|
||||
child process.
|
||||
displaced_step_finish (ecs->event_thread, ecs->ws);
|
||||
|
||||
Architectures which support displaced stepping and fork
|
||||
events must supply an implementation of
|
||||
gdbarch_displaced_step_restore_all_in_ptid. This is not
|
||||
enforced during gdbarch validation to support architectures
|
||||
which support displaced stepping but not forks. */
|
||||
if (ecs->ws.kind () == TARGET_WAITKIND_FORKED
|
||||
&& gdbarch_supports_displaced_stepping (gdbarch))
|
||||
gdbarch_displaced_step_restore_all_in_ptid
|
||||
(gdbarch, parent_inf, ecs->ws.child_ptid ());
|
||||
|
||||
/* If displaced stepping is supported, and thread ecs->ptid is
|
||||
displaced stepping. */
|
||||
if (displaced_step_in_progress_thread (ecs->event_thread))
|
||||
{
|
||||
struct regcache *child_regcache;
|
||||
CORE_ADDR parent_pc;
|
||||
|
||||
/* GDB has got TARGET_WAITKIND_FORKED or TARGET_WAITKIND_VFORKED,
|
||||
indicating that the displaced stepping of syscall instruction
|
||||
has been done. Perform cleanup for parent process here. Note
|
||||
that this operation also cleans up the child process for vfork,
|
||||
because their pages are shared. */
|
||||
displaced_step_finish (ecs->event_thread, GDB_SIGNAL_TRAP);
|
||||
/* Start a new step-over in another thread if there's one
|
||||
that needs it. */
|
||||
start_step_over ();
|
||||
|
||||
/* Since the vfork/fork syscall instruction was executed in the scratchpad,
|
||||
the child's PC is also within the scratchpad. Set the child's PC
|
||||
to the parent's PC value, which has already been fixed up.
|
||||
FIXME: we use the parent's aspace here, although we're touching
|
||||
the child, because the child hasn't been added to the inferior
|
||||
list yet at this point. */
|
||||
|
||||
child_regcache
|
||||
= get_thread_arch_aspace_regcache (parent_inf->process_target (),
|
||||
ecs->ws.child_ptid (),
|
||||
gdbarch,
|
||||
parent_inf->aspace);
|
||||
/* Read PC value of parent process. */
|
||||
parent_pc = regcache_read_pc (regcache);
|
||||
|
||||
displaced_debug_printf ("write child pc from %s to %s",
|
||||
paddress (gdbarch,
|
||||
regcache_read_pc (child_regcache)),
|
||||
paddress (gdbarch, parent_pc));
|
||||
|
||||
regcache_write_pc (child_regcache, parent_pc);
|
||||
}
|
||||
}
|
||||
/* Start a new step-over in another thread if there's one that
|
||||
needs it. */
|
||||
start_step_over ();
|
||||
|
||||
context_switch (ecs);
|
||||
|
||||
@@ -5796,7 +6177,7 @@ handle_inferior_event (struct execution_control_state *ecs)
|
||||
need to unpatch at follow/detach time instead to be certain
|
||||
that new breakpoints added between catchpoint hit time and
|
||||
vfork follow are detached. */
|
||||
if (ecs->ws.kind () != TARGET_WAITKIND_VFORKED)
|
||||
if (ecs->ws.kind () == TARGET_WAITKIND_FORKED)
|
||||
{
|
||||
/* This won't actually modify the breakpoint list, but will
|
||||
physically remove the breakpoints from the child. */
|
||||
@@ -5828,14 +6209,24 @@ handle_inferior_event (struct execution_control_state *ecs)
|
||||
if (!bpstat_causes_stop (ecs->event_thread->control.stop_bpstat))
|
||||
{
|
||||
bool follow_child
|
||||
= (follow_fork_mode_string == follow_fork_mode_child);
|
||||
= (ecs->ws.kind () != TARGET_WAITKIND_THREAD_CLONED
|
||||
&& follow_fork_mode_string == follow_fork_mode_child);
|
||||
|
||||
ecs->event_thread->set_stop_signal (GDB_SIGNAL_0);
|
||||
|
||||
process_stratum_target *targ
|
||||
= ecs->event_thread->inf->process_target ();
|
||||
|
||||
bool should_resume = follow_fork ();
|
||||
bool should_resume;
|
||||
if (ecs->ws.kind () != TARGET_WAITKIND_THREAD_CLONED)
|
||||
should_resume = follow_fork ();
|
||||
else
|
||||
{
|
||||
should_resume = true;
|
||||
inferior *inf = ecs->event_thread->inf;
|
||||
inf->top_target ()->follow_clone (ecs->ws.child_ptid ());
|
||||
ecs->event_thread->pending_follow.set_spurious ();
|
||||
}
|
||||
|
||||
/* Note that one of these may be an invalid pointer,
|
||||
depending on detach_fork. */
|
||||
@@ -5846,16 +6237,26 @@ handle_inferior_event (struct execution_control_state *ecs)
|
||||
child is marked stopped. */
|
||||
|
||||
/* If not resuming the parent, mark it stopped. */
|
||||
if (follow_child && !detach_fork && !non_stop && !sched_multi)
|
||||
if (ecs->ws.kind () != TARGET_WAITKIND_THREAD_CLONED
|
||||
&& follow_child && !detach_fork && !non_stop && !sched_multi)
|
||||
parent->set_running (false);
|
||||
|
||||
/* If resuming the child, mark it running. */
|
||||
if (follow_child || (!detach_fork && (non_stop || sched_multi)))
|
||||
if ((ecs->ws.kind () == TARGET_WAITKIND_THREAD_CLONED
|
||||
&& !schedlock_applies (ecs->event_thread))
|
||||
|| (ecs->ws.kind () != TARGET_WAITKIND_THREAD_CLONED
|
||||
&& (follow_child
|
||||
|| (!detach_fork && (non_stop || sched_multi)))))
|
||||
child->set_running (true);
|
||||
|
||||
/* In non-stop mode, also resume the other branch. */
|
||||
if (!detach_fork && (non_stop
|
||||
|| (sched_multi && target_is_non_stop_p ())))
|
||||
if ((ecs->ws.kind () == TARGET_WAITKIND_THREAD_CLONED
|
||||
&& target_is_non_stop_p ()
|
||||
&& !schedlock_applies (ecs->event_thread))
|
||||
|| (ecs->ws.kind () != TARGET_WAITKIND_THREAD_CLONED
|
||||
&& (!detach_fork && (non_stop
|
||||
|| (sched_multi
|
||||
&& target_is_non_stop_p ())))))
|
||||
{
|
||||
if (follow_child)
|
||||
switch_to_thread (parent);
|
||||
@@ -6118,7 +6519,7 @@ resumed_thread_with_pending_status (struct thread_info *tp,
|
||||
static int
|
||||
finish_step_over (struct execution_control_state *ecs)
|
||||
{
|
||||
displaced_step_finish (ecs->event_thread, ecs->event_thread->stop_signal ());
|
||||
displaced_step_finish (ecs->event_thread, ecs->ws);
|
||||
|
||||
bool had_step_over_info = step_over_info_valid_p ();
|
||||
|
||||
@@ -6129,6 +6530,8 @@ finish_step_over (struct execution_control_state *ecs)
|
||||
back an event. */
|
||||
gdb_assert (ecs->event_thread->control.trap_expected);
|
||||
|
||||
update_thread_events_after_step_over (ecs->event_thread, ecs->ws);
|
||||
|
||||
clear_step_over_info ();
|
||||
}
|
||||
|
||||
@@ -6173,6 +6576,13 @@ finish_step_over (struct execution_control_state *ecs)
|
||||
if (ecs->event_thread->stepping_over_watchpoint)
|
||||
return 0;
|
||||
|
||||
/* The code below is meant to avoid one thread hogging the event
|
||||
loop by doing constant in-line step overs. If the stepping
|
||||
thread exited, there's no risk for this to happen, so we can
|
||||
safely let our caller process the event immediately. */
|
||||
if (ecs->ws.kind () == TARGET_WAITKIND_THREAD_EXITED)
|
||||
return 0;
|
||||
|
||||
pending = iterate_over_threads (resumed_thread_with_pending_status,
|
||||
nullptr);
|
||||
if (pending != nullptr)
|
||||
@@ -8753,7 +9163,8 @@ normal_stop ()
|
||||
if (inferior_ptid != null_ptid)
|
||||
finish_ptid = ptid_t (inferior_ptid.pid ());
|
||||
}
|
||||
else if (last.kind () != TARGET_WAITKIND_NO_RESUMED)
|
||||
else if (last.kind () != TARGET_WAITKIND_NO_RESUMED
|
||||
&& last.kind () != TARGET_WAITKIND_THREAD_EXITED)
|
||||
finish_ptid = inferior_ptid;
|
||||
|
||||
gdb::optional<scoped_finish_thread_state> maybe_finish_thread_state;
|
||||
@@ -8796,7 +9207,8 @@ normal_stop ()
|
||||
{
|
||||
if ((last.kind () != TARGET_WAITKIND_SIGNALLED
|
||||
&& last.kind () != TARGET_WAITKIND_EXITED
|
||||
&& last.kind () != TARGET_WAITKIND_NO_RESUMED)
|
||||
&& last.kind () != TARGET_WAITKIND_NO_RESUMED
|
||||
&& last.kind () != TARGET_WAITKIND_THREAD_EXITED)
|
||||
&& target_has_execution ()
|
||||
&& previous_thread != inferior_thread ())
|
||||
{
|
||||
@@ -8812,13 +9224,21 @@ normal_stop ()
|
||||
update_previous_thread ();
|
||||
}
|
||||
|
||||
if (last.kind () == TARGET_WAITKIND_NO_RESUMED)
|
||||
if (last.kind () == TARGET_WAITKIND_NO_RESUMED
|
||||
|| last.kind () == TARGET_WAITKIND_THREAD_EXITED)
|
||||
{
|
||||
stop_print_frame = false;
|
||||
|
||||
SWITCH_THRU_ALL_UIS ()
|
||||
if (current_ui->prompt_state == PROMPT_BLOCKED)
|
||||
{
|
||||
target_terminal::ours_for_output ();
|
||||
gdb_printf (_("No unwaited-for children left.\n"));
|
||||
if (last.kind () == TARGET_WAITKIND_NO_RESUMED)
|
||||
gdb_printf (_("No unwaited-for children left.\n"));
|
||||
else if (last.kind () == TARGET_WAITKIND_THREAD_EXITED)
|
||||
gdb_printf (_("Command aborted, thread exited.\n"));
|
||||
else
|
||||
gdb_assert_not_reached ("unhandled");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8903,7 +9323,8 @@ normal_stop ()
|
||||
{
|
||||
if (last.kind () != TARGET_WAITKIND_SIGNALLED
|
||||
&& last.kind () != TARGET_WAITKIND_EXITED
|
||||
&& last.kind () != TARGET_WAITKIND_NO_RESUMED)
|
||||
&& last.kind () != TARGET_WAITKIND_NO_RESUMED
|
||||
&& last.kind () != TARGET_WAITKIND_THREAD_EXITED)
|
||||
/* Delete the breakpoint we stopped at, if it wants to be deleted.
|
||||
Delete any breakpoint that is to be deleted at the next stop. */
|
||||
breakpoint_auto_delete (inferior_thread ()->control.stop_bpstat);
|
||||
|
||||
388
gdb/linux-nat.c
388
gdb/linux-nat.c
@@ -255,6 +255,31 @@ is_leader (lwp_info *lp)
|
||||
return lp->ptid.pid () == lp->ptid.lwp ();
|
||||
}
|
||||
|
||||
/* Convert an LWP's pending status to a std::string. */
|
||||
|
||||
static std::string
|
||||
pending_status_str (lwp_info *lp)
|
||||
{
|
||||
gdb_assert (lwp_status_pending_p (lp));
|
||||
|
||||
if (lp->waitstatus.kind () != TARGET_WAITKIND_IGNORE)
|
||||
return lp->waitstatus.to_string ();
|
||||
else
|
||||
return status_to_str (lp->status);
|
||||
}
|
||||
|
||||
/* Return true if we should report exit events for LP. */
|
||||
|
||||
static bool
|
||||
report_exit_events_for (lwp_info *lp)
|
||||
{
|
||||
thread_info *thr = find_thread_ptid (linux_target, lp->ptid);
|
||||
gdb_assert (thr != nullptr);
|
||||
|
||||
return (report_thread_events
|
||||
|| (thr->thread_options () & GDB_THREAD_OPTION_EXIT) != 0);
|
||||
}
|
||||
|
||||
|
||||
/* LWP accessors. */
|
||||
|
||||
@@ -885,20 +910,17 @@ linux_nat_switch_fork (ptid_t new_ptid)
|
||||
registers_changed ();
|
||||
}
|
||||
|
||||
/* Handle the exit of a single thread LP. */
|
||||
/* Handle the exit of a single thread LP. If DEL_THREAD is true,
|
||||
delete the thread_info associated to LP, if it exists. */
|
||||
|
||||
static void
|
||||
exit_lwp (struct lwp_info *lp)
|
||||
exit_lwp (struct lwp_info *lp, bool del_thread = true)
|
||||
{
|
||||
struct thread_info *th = find_thread_ptid (linux_target, lp->ptid);
|
||||
|
||||
if (th)
|
||||
if (del_thread)
|
||||
{
|
||||
if (print_thread_events)
|
||||
gdb_printf (_("[%s exited]\n"),
|
||||
target_pid_to_str (lp->ptid).c_str ());
|
||||
|
||||
delete_thread (th);
|
||||
thread_info *th = find_thread_ptid (linux_target, lp->ptid);
|
||||
if (th != nullptr)
|
||||
delete_thread (th);
|
||||
}
|
||||
|
||||
delete_lwp (lp->ptid);
|
||||
@@ -1273,6 +1295,63 @@ get_detach_signal (struct lwp_info *lp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If LP has a pending fork/vfork/clone status, return it. */
|
||||
|
||||
static gdb::optional<target_waitstatus>
|
||||
get_pending_child_status (lwp_info *lp)
|
||||
{
|
||||
/* Check in lwp_info::status. */
|
||||
if (WIFSTOPPED (lp->status) && linux_is_extended_waitstatus (lp->status))
|
||||
{
|
||||
int event = linux_ptrace_get_extended_event (lp->status);
|
||||
|
||||
if (event == PTRACE_EVENT_FORK
|
||||
|| event == PTRACE_EVENT_VFORK
|
||||
|| event == PTRACE_EVENT_CLONE)
|
||||
{
|
||||
unsigned long child_pid;
|
||||
int ret = ptrace (PTRACE_GETEVENTMSG, lp->ptid.lwp (), 0, &child_pid);
|
||||
if (ret == 0)
|
||||
{
|
||||
target_waitstatus ws;
|
||||
|
||||
if (event == PTRACE_EVENT_FORK)
|
||||
ws.set_forked (ptid_t (child_pid, child_pid));
|
||||
else if (event == PTRACE_EVENT_VFORK)
|
||||
ws.set_vforked (ptid_t (child_pid, child_pid));
|
||||
else if (event == PTRACE_EVENT_CLONE)
|
||||
ws.set_thread_cloned (ptid_t (lp->ptid.pid (), child_pid));
|
||||
else
|
||||
gdb_assert_not_reached ("unhandled");
|
||||
|
||||
return ws;
|
||||
}
|
||||
else
|
||||
{
|
||||
perror_warning_with_name (_("Failed to retrieve event msg"));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check in lwp_info::waitstatus. */
|
||||
if (is_new_child_status (lp->waitstatus.kind ()))
|
||||
return lp->waitstatus;
|
||||
|
||||
thread_info *tp = find_thread_ptid (linux_target, lp->ptid);
|
||||
|
||||
/* Check in thread_info::pending_waitstatus. */
|
||||
if (tp->has_pending_waitstatus ()
|
||||
&& is_new_child_status (tp->pending_waitstatus ().kind ()))
|
||||
return tp->pending_waitstatus ();
|
||||
|
||||
/* Check in thread_info::pending_follow. */
|
||||
if (is_new_child_status (tp->pending_follow.kind ()))
|
||||
return tp->pending_follow;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/* Detach from LP. If SIGNO_P is non-NULL, then it points to the
|
||||
signal number that should be passed to the LWP when detaching.
|
||||
Otherwise pass any pending signal the LWP may have, if any. */
|
||||
@@ -1283,54 +1362,13 @@ detach_one_lwp (struct lwp_info *lp, int *signo_p)
|
||||
int lwpid = lp->ptid.lwp ();
|
||||
int signo;
|
||||
|
||||
gdb_assert (lp->status == 0 || WIFSTOPPED (lp->status));
|
||||
/* If the lwp/thread we are about to detach has a pending fork/clone
|
||||
event, there is a process/thread GDB is attached to that the core
|
||||
of GDB doesn't know about. Detach from it. */
|
||||
|
||||
/* If the lwp/thread we are about to detach has a pending fork event,
|
||||
there is a process GDB is attached to that the core of GDB doesn't know
|
||||
about. Detach from it. */
|
||||
|
||||
/* Check in lwp_info::status. */
|
||||
if (WIFSTOPPED (lp->status) && linux_is_extended_waitstatus (lp->status))
|
||||
{
|
||||
int event = linux_ptrace_get_extended_event (lp->status);
|
||||
|
||||
if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
|
||||
{
|
||||
unsigned long child_pid;
|
||||
int ret = ptrace (PTRACE_GETEVENTMSG, lp->ptid.lwp (), 0, &child_pid);
|
||||
if (ret == 0)
|
||||
detach_one_pid (child_pid, 0);
|
||||
else
|
||||
perror_warning_with_name (_("Failed to detach fork child"));
|
||||
}
|
||||
}
|
||||
|
||||
/* Check in lwp_info::waitstatus. */
|
||||
if (lp->waitstatus.kind () == TARGET_WAITKIND_VFORKED
|
||||
|| lp->waitstatus.kind () == TARGET_WAITKIND_FORKED)
|
||||
detach_one_pid (lp->waitstatus.child_ptid ().pid (), 0);
|
||||
|
||||
|
||||
/* Check in thread_info::pending_waitstatus. */
|
||||
thread_info *tp = find_thread_ptid (linux_target, lp->ptid);
|
||||
if (tp->has_pending_waitstatus ())
|
||||
{
|
||||
const target_waitstatus &ws = tp->pending_waitstatus ();
|
||||
|
||||
if (ws.kind () == TARGET_WAITKIND_VFORKED
|
||||
|| ws.kind () == TARGET_WAITKIND_FORKED)
|
||||
detach_one_pid (ws.child_ptid ().pid (), 0);
|
||||
}
|
||||
|
||||
/* Check in thread_info::pending_follow. */
|
||||
if (tp->pending_follow.kind () == TARGET_WAITKIND_VFORKED
|
||||
|| tp->pending_follow.kind () == TARGET_WAITKIND_FORKED)
|
||||
detach_one_pid (tp->pending_follow.child_ptid ().pid (), 0);
|
||||
|
||||
if (lp->status != 0)
|
||||
linux_nat_debug_printf ("Pending %s for %s on detach.",
|
||||
strsignal (WSTOPSIG (lp->status)),
|
||||
lp->ptid.to_string ().c_str ());
|
||||
gdb::optional<target_waitstatus> ws = get_pending_child_status (lp);
|
||||
if (ws.has_value ())
|
||||
detach_one_pid (ws->child_ptid ().lwp (), 0);
|
||||
|
||||
/* If there is a pending SIGSTOP, get rid of it. */
|
||||
if (lp->signalled)
|
||||
@@ -1647,8 +1685,8 @@ linux_nat_target::resume (ptid_t scope_ptid, int step, enum gdb_signal signo)
|
||||
this thread with a signal? */
|
||||
gdb_assert (signo == GDB_SIGNAL_0);
|
||||
|
||||
linux_nat_debug_printf ("Short circuiting for status 0x%x",
|
||||
lp->status);
|
||||
linux_nat_debug_printf ("Short circuiting for status %s",
|
||||
pending_status_str (lp).c_str ());
|
||||
|
||||
if (target_can_async_p ())
|
||||
{
|
||||
@@ -1808,6 +1846,55 @@ linux_handle_syscall_trap (struct lwp_info *lp, int stopping)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* See target.h. */
|
||||
|
||||
void
|
||||
linux_nat_target::follow_clone (ptid_t child_ptid)
|
||||
{
|
||||
lwp_info *new_lp = add_lwp (child_ptid);
|
||||
new_lp->stopped = 1;
|
||||
|
||||
/* If the thread_db layer is active, let it record the user
|
||||
level thread id and status, and add the thread to GDB's
|
||||
list. */
|
||||
if (!thread_db_notice_clone (inferior_ptid, new_lp->ptid))
|
||||
{
|
||||
/* The process is not using thread_db. Add the LWP to
|
||||
GDB's list. */
|
||||
add_thread (linux_target, new_lp->ptid);
|
||||
}
|
||||
|
||||
/* We just created NEW_LP so it cannot yet contain STATUS. */
|
||||
gdb_assert (new_lp->status == 0);
|
||||
|
||||
if (!pull_pid_from_list (&stopped_pids, child_ptid.lwp (), &new_lp->status))
|
||||
internal_error (_("no saved status for clone lwp"));
|
||||
|
||||
if (WSTOPSIG (new_lp->status) != SIGSTOP)
|
||||
{
|
||||
/* This can happen if someone starts sending signals to
|
||||
the new thread before it gets a chance to run, which
|
||||
have a lower number than SIGSTOP (e.g. SIGUSR1).
|
||||
This is an unlikely case, and harder to handle for
|
||||
fork / vfork than for clone, so we do not try - but
|
||||
we handle it for clone events here. */
|
||||
|
||||
new_lp->signalled = 1;
|
||||
|
||||
/* Save the wait status to report later. */
|
||||
linux_nat_debug_printf
|
||||
("waitpid of new LWP %ld, saving status %s",
|
||||
(long) new_lp->ptid.lwp (), status_to_str (new_lp->status).c_str ());
|
||||
}
|
||||
else
|
||||
{
|
||||
new_lp->status = 0;
|
||||
|
||||
if (report_thread_events)
|
||||
new_lp->waitstatus.set_thread_created ();
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle a GNU/Linux extended wait response. If we see a clone
|
||||
event, we need to add the new LWP to our list (and not report the
|
||||
trap to higher layers). This function returns non-zero if the
|
||||
@@ -1848,11 +1935,9 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
|
||||
internal_error (_("wait returned unexpected status 0x%x"), status);
|
||||
}
|
||||
|
||||
ptid_t child_ptid (new_pid, new_pid);
|
||||
|
||||
if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
|
||||
{
|
||||
open_proc_mem_file (child_ptid);
|
||||
open_proc_mem_file (ptid_t (new_pid, new_pid));
|
||||
|
||||
/* The arch-specific native code may need to know about new
|
||||
forks even if those end up never mapped to an
|
||||
@@ -1889,66 +1974,18 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
|
||||
}
|
||||
|
||||
if (event == PTRACE_EVENT_FORK)
|
||||
ourstatus->set_forked (child_ptid);
|
||||
ourstatus->set_forked (ptid_t (new_pid, new_pid));
|
||||
else if (event == PTRACE_EVENT_VFORK)
|
||||
ourstatus->set_vforked (child_ptid);
|
||||
ourstatus->set_vforked (ptid_t (new_pid, new_pid));
|
||||
else if (event == PTRACE_EVENT_CLONE)
|
||||
{
|
||||
struct lwp_info *new_lp;
|
||||
|
||||
ourstatus->set_ignore ();
|
||||
|
||||
linux_nat_debug_printf
|
||||
("Got clone event from LWP %d, new child is LWP %ld", pid, new_pid);
|
||||
|
||||
new_lp = add_lwp (ptid_t (lp->ptid.pid (), new_pid));
|
||||
new_lp->stopped = 1;
|
||||
new_lp->resumed = 1;
|
||||
/* Save the status again, we'll use it in follow_clone. */
|
||||
add_to_pid_list (&stopped_pids, new_pid, status);
|
||||
|
||||
/* If the thread_db layer is active, let it record the user
|
||||
level thread id and status, and add the thread to GDB's
|
||||
list. */
|
||||
if (!thread_db_notice_clone (lp->ptid, new_lp->ptid))
|
||||
{
|
||||
/* The process is not using thread_db. Add the LWP to
|
||||
GDB's list. */
|
||||
add_thread (linux_target, new_lp->ptid);
|
||||
}
|
||||
|
||||
/* Even if we're stopping the thread for some reason
|
||||
internal to this module, from the perspective of infrun
|
||||
and the user/frontend, this new thread is running until
|
||||
it next reports a stop. */
|
||||
set_running (linux_target, new_lp->ptid, true);
|
||||
set_executing (linux_target, new_lp->ptid, true);
|
||||
|
||||
if (WSTOPSIG (status) != SIGSTOP)
|
||||
{
|
||||
/* This can happen if someone starts sending signals to
|
||||
the new thread before it gets a chance to run, which
|
||||
have a lower number than SIGSTOP (e.g. SIGUSR1).
|
||||
This is an unlikely case, and harder to handle for
|
||||
fork / vfork than for clone, so we do not try - but
|
||||
we handle it for clone events here. */
|
||||
|
||||
new_lp->signalled = 1;
|
||||
|
||||
/* We created NEW_LP so it cannot yet contain STATUS. */
|
||||
gdb_assert (new_lp->status == 0);
|
||||
|
||||
/* Save the wait status to report later. */
|
||||
linux_nat_debug_printf
|
||||
("waitpid of new LWP %ld, saving status %s",
|
||||
(long) new_lp->ptid.lwp (), status_to_str (status).c_str ());
|
||||
new_lp->status = status;
|
||||
}
|
||||
else if (report_thread_events)
|
||||
{
|
||||
new_lp->waitstatus.set_thread_created ();
|
||||
new_lp->status = status;
|
||||
}
|
||||
|
||||
return 1;
|
||||
ourstatus->set_thread_cloned (ptid_t (lp->ptid.pid (), new_pid));
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1973,6 +2010,21 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
|
||||
thread execs, it changes its tid to the tgid, and the old
|
||||
tgid thread might have not been resumed. */
|
||||
lp->resumed = 1;
|
||||
|
||||
/* All other LWPs are gone now. We'll have received a thread
|
||||
exit notification for all threads other the execing one.
|
||||
That one, if it wasn't the leader, just silently changes its
|
||||
tid to the tgid, and the previous leader vanishes. Since
|
||||
Linux 3.0, the former thread ID can be retrieved with
|
||||
PTRACE_GETEVENTMSG, but since we support older kernels, don't
|
||||
bother with it, and just walk the LWP list. Even with
|
||||
PTRACE_GETEVENTMSG, we'd still need to lookup the
|
||||
corresponding LWP object, and it would be an extra ptrace
|
||||
syscall, so this way may even be more efficient. */
|
||||
for (lwp_info *other_lp : all_lwps_safe ())
|
||||
if (other_lp != lp && other_lp->ptid.pid () == lp->ptid.pid ())
|
||||
exit_lwp (other_lp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2089,8 +2141,7 @@ wait_lwp (struct lwp_info *lp)
|
||||
/* Check if the thread has exited. */
|
||||
if (WIFEXITED (status) || WIFSIGNALED (status))
|
||||
{
|
||||
if (report_thread_events
|
||||
|| lp->ptid.pid () == lp->ptid.lwp ())
|
||||
if (report_exit_events_for (lp) || is_leader (lp))
|
||||
{
|
||||
linux_nat_debug_printf ("LWP %d exited.", lp->ptid.pid ());
|
||||
|
||||
@@ -2871,7 +2922,7 @@ linux_nat_filter_event (int lwpid, int status)
|
||||
/* Check if the thread has exited. */
|
||||
if (WIFEXITED (status) || WIFSIGNALED (status))
|
||||
{
|
||||
if (!report_thread_events && !is_leader (lp))
|
||||
if (!report_exit_events_for (lp) && !is_leader (lp))
|
||||
{
|
||||
linux_nat_debug_printf ("%s exited.",
|
||||
lp->ptid.to_string ().c_str ());
|
||||
@@ -3081,10 +3132,11 @@ check_zombie_leaders (void)
|
||||
}
|
||||
}
|
||||
|
||||
/* Convenience function that is called when the kernel reports an exit
|
||||
event. This decides whether to report the event to GDB as a
|
||||
process exit event, a thread exit event, or to suppress the
|
||||
event. */
|
||||
/* Convenience function that is called when we're about to return an
|
||||
event to the core. If the event is an exit or signalled event,
|
||||
then this decides whether to report it as process-wide event, as a
|
||||
thread exit event, or to suppress it. All other event kinds are
|
||||
passed through unmodified. */
|
||||
|
||||
static ptid_t
|
||||
filter_exit_event (struct lwp_info *event_child,
|
||||
@@ -3092,14 +3144,28 @@ filter_exit_event (struct lwp_info *event_child,
|
||||
{
|
||||
ptid_t ptid = event_child->ptid;
|
||||
|
||||
/* Note we must filter TARGET_WAITKIND_SIGNALLED as well, otherwise
|
||||
if a non-leader thread exits with a signal, we'd report it to the
|
||||
core which would interpret it as the whole-process exiting.
|
||||
There is no TARGET_WAITKIND_THREAD_SIGNALLED event kind. */
|
||||
if (ourstatus->kind () != TARGET_WAITKIND_EXITED
|
||||
&& ourstatus->kind () != TARGET_WAITKIND_SIGNALLED)
|
||||
return ptid;
|
||||
|
||||
if (!is_leader (event_child))
|
||||
{
|
||||
if (report_thread_events)
|
||||
ourstatus->set_thread_exited (0);
|
||||
if (report_exit_events_for (event_child))
|
||||
{
|
||||
ourstatus->set_thread_exited (0);
|
||||
/* Delete lwp, but not thread_info, infrun will need it to
|
||||
process the event. */
|
||||
exit_lwp (event_child, false);
|
||||
}
|
||||
else
|
||||
ourstatus->set_ignore ();
|
||||
|
||||
exit_lwp (event_child);
|
||||
{
|
||||
ourstatus->set_ignore ();
|
||||
exit_lwp (event_child);
|
||||
}
|
||||
}
|
||||
|
||||
return ptid;
|
||||
@@ -3137,7 +3203,7 @@ linux_nat_wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus,
|
||||
if (lp != NULL)
|
||||
{
|
||||
linux_nat_debug_printf ("Using pending wait status %s for %s.",
|
||||
status_to_str (lp->status).c_str (),
|
||||
pending_status_str (lp).c_str (),
|
||||
lp->ptid.to_string ().c_str ());
|
||||
}
|
||||
|
||||
@@ -3321,10 +3387,7 @@ linux_nat_wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus,
|
||||
else
|
||||
lp->core = linux_common_core_of_thread (lp->ptid);
|
||||
|
||||
if (ourstatus->kind () == TARGET_WAITKIND_EXITED)
|
||||
return filter_exit_event (lp, ourstatus);
|
||||
|
||||
return lp->ptid;
|
||||
return filter_exit_event (lp, ourstatus);
|
||||
}
|
||||
|
||||
/* Resume LWPs that are currently stopped without any pending status
|
||||
@@ -3508,59 +3571,56 @@ kill_wait_callback (struct lwp_info *lp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Kill the fork children of any threads of inferior INF that are
|
||||
stopped at a fork event. */
|
||||
/* Kill the fork/clone child of LP if it has an unfollowed child. */
|
||||
|
||||
static void
|
||||
kill_unfollowed_fork_children (struct inferior *inf)
|
||||
static int
|
||||
kill_unfollowed_child_callback (lwp_info *lp)
|
||||
{
|
||||
for (thread_info *thread : inf->non_exited_threads ())
|
||||
gdb::optional<target_waitstatus> ws = get_pending_child_status (lp);
|
||||
if (ws.has_value ())
|
||||
{
|
||||
struct target_waitstatus *ws = &thread->pending_follow;
|
||||
ptid_t child_ptid = ws->child_ptid ();
|
||||
int child_pid = child_ptid.pid ();
|
||||
int child_lwp = child_ptid.lwp ();
|
||||
|
||||
if (ws->kind () == TARGET_WAITKIND_FORKED
|
||||
|| ws->kind () == TARGET_WAITKIND_VFORKED)
|
||||
{
|
||||
ptid_t child_ptid = ws->child_ptid ();
|
||||
int child_pid = child_ptid.pid ();
|
||||
int child_lwp = child_ptid.lwp ();
|
||||
kill_one_lwp (child_lwp);
|
||||
kill_wait_one_lwp (child_lwp);
|
||||
|
||||
kill_one_lwp (child_lwp);
|
||||
kill_wait_one_lwp (child_lwp);
|
||||
|
||||
/* Let the arch-specific native code know this process is
|
||||
gone. */
|
||||
linux_target->low_forget_process (child_pid);
|
||||
}
|
||||
/* Let the arch-specific native code know this process is
|
||||
gone. */
|
||||
if (ws->kind () != TARGET_WAITKIND_THREAD_CLONED)
|
||||
linux_target->low_forget_process (child_pid);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
linux_nat_target::kill ()
|
||||
{
|
||||
/* If we're stopped while forking and we haven't followed yet,
|
||||
kill the other task. We need to do this first because the
|
||||
ptid_t pid_ptid (inferior_ptid.pid ());
|
||||
|
||||
/* If we're stopped while forking/cloning and we haven't followed
|
||||
yet, kill the child task. We need to do this first because the
|
||||
parent will be sleeping if this is a vfork. */
|
||||
kill_unfollowed_fork_children (current_inferior ());
|
||||
iterate_over_lwps (pid_ptid, kill_unfollowed_child_callback);
|
||||
|
||||
if (forks_exist_p ())
|
||||
linux_fork_killall ();
|
||||
else
|
||||
{
|
||||
ptid_t ptid = ptid_t (inferior_ptid.pid ());
|
||||
|
||||
/* Stop all threads before killing them, since ptrace requires
|
||||
that the thread is stopped to successfully PTRACE_KILL. */
|
||||
iterate_over_lwps (ptid, stop_callback);
|
||||
iterate_over_lwps (pid_ptid, stop_callback);
|
||||
/* ... and wait until all of them have reported back that
|
||||
they're no longer running. */
|
||||
iterate_over_lwps (ptid, stop_wait_callback);
|
||||
iterate_over_lwps (pid_ptid, stop_wait_callback);
|
||||
|
||||
/* Kill all LWP's ... */
|
||||
iterate_over_lwps (ptid, kill_callback);
|
||||
iterate_over_lwps (pid_ptid, kill_callback);
|
||||
|
||||
/* ... and wait until we've flushed all events. */
|
||||
iterate_over_lwps (ptid, kill_wait_callback);
|
||||
iterate_over_lwps (pid_ptid, kill_wait_callback);
|
||||
}
|
||||
|
||||
target_mourn_inferior (inferior_ptid);
|
||||
@@ -4427,6 +4487,14 @@ linux_nat_target::thread_events (int enable)
|
||||
report_thread_events = enable;
|
||||
}
|
||||
|
||||
bool
|
||||
linux_nat_target::supports_set_thread_options (gdb_thread_options options)
|
||||
{
|
||||
constexpr gdb_thread_options supported_options
|
||||
= GDB_THREAD_OPTION_CLONE | GDB_THREAD_OPTION_EXIT;
|
||||
return ((options & supported_options) == options);
|
||||
}
|
||||
|
||||
linux_nat_target::linux_nat_target ()
|
||||
{
|
||||
/* We don't change the stratum; this target will sit at
|
||||
|
||||
@@ -82,6 +82,8 @@ public:
|
||||
|
||||
void thread_events (int) override;
|
||||
|
||||
bool supports_set_thread_options (gdb_thread_options options) override;
|
||||
|
||||
bool can_async_p () override;
|
||||
|
||||
bool supports_non_stop () override;
|
||||
@@ -129,6 +131,8 @@ public:
|
||||
|
||||
void follow_fork (inferior *, ptid_t, target_waitkind, bool, bool) override;
|
||||
|
||||
void follow_clone (ptid_t) override;
|
||||
|
||||
std::vector<static_tracepoint_marker>
|
||||
static_tracepoint_markers_by_strid (const char *id) override;
|
||||
|
||||
@@ -232,7 +236,9 @@ struct lwp_info : intrusive_list_node<lwp_info>
|
||||
/* The last resume GDB requested on this thread. */
|
||||
resume_kind last_resume_kind = resume_continue;
|
||||
|
||||
/* If non-zero, a pending wait status. */
|
||||
/* If non-zero, a pending wait status. A pending process exit is
|
||||
recorded in WAITSTATUS, because W_EXITCODE(0,0) happens to be
|
||||
0. */
|
||||
int status = 0;
|
||||
|
||||
/* When 'stopped' is set, this is where the lwp last stopped, with
|
||||
@@ -260,9 +266,10 @@ struct lwp_info : intrusive_list_node<lwp_info>
|
||||
/* Non-zero if we expect a duplicated SIGINT. */
|
||||
int ignore_sigint = 0;
|
||||
|
||||
/* If WAITSTATUS->KIND != TARGET_WAITKIND_SPURIOUS, the waitstatus
|
||||
for this LWP's last event. This may correspond to STATUS above,
|
||||
or to a local variable in lin_lwp_wait. */
|
||||
/* If WAITSTATUS->KIND != TARGET_WAITKIND_IGNORE, the waitstatus for
|
||||
this LWP's last event. This usually corresponds to STATUS above,
|
||||
however because W_EXITCODE(0,0) happens to be 0, a process exit
|
||||
will be recorded here, while 'status == 0' is ambiguous. */
|
||||
struct target_waitstatus waitstatus;
|
||||
|
||||
/* Signal whether we are in a SYSCALL_ENTRY or
|
||||
|
||||
@@ -2621,13 +2621,14 @@ linux_displaced_step_prepare (gdbarch *arch, thread_info *thread,
|
||||
/* See linux-tdep.h. */
|
||||
|
||||
displaced_step_finish_status
|
||||
linux_displaced_step_finish (gdbarch *arch, thread_info *thread, gdb_signal sig)
|
||||
linux_displaced_step_finish (gdbarch *arch, thread_info *thread,
|
||||
const target_waitstatus &status)
|
||||
{
|
||||
linux_info *per_inferior = get_linux_inferior_data (thread->inf);
|
||||
|
||||
gdb_assert (per_inferior->disp_step_bufs.has_value ());
|
||||
|
||||
return per_inferior->disp_step_bufs->finish (arch, thread, sig);
|
||||
return per_inferior->disp_step_bufs->finish (arch, thread, status);
|
||||
}
|
||||
|
||||
/* See linux-tdep.h. */
|
||||
|
||||
@@ -72,7 +72,7 @@ extern displaced_step_prepare_status linux_displaced_step_prepare
|
||||
/* Implementation of gdbarch_displaced_step_finish. */
|
||||
|
||||
extern displaced_step_finish_status linux_displaced_step_finish
|
||||
(gdbarch *arch, thread_info *thread, gdb_signal sig);
|
||||
(gdbarch *arch, thread_info *thread, const target_waitstatus &status);
|
||||
|
||||
/* Implementation of gdbarch_displaced_step_copy_insn_closure_by_addr. */
|
||||
|
||||
|
||||
@@ -68,7 +68,9 @@ static void mi_on_normal_stop (struct bpstat *bs, int print_frame);
|
||||
static void mi_on_no_history (void);
|
||||
|
||||
static void mi_new_thread (struct thread_info *t);
|
||||
static void mi_thread_exit (struct thread_info *t, int silent);
|
||||
static void mi_thread_exit (thread_info *t,
|
||||
gdb::optional<ULONGEST> exit_code,
|
||||
bool silent);
|
||||
static void mi_record_changed (struct inferior*, int, const char *,
|
||||
const char *);
|
||||
static void mi_inferior_added (struct inferior *inf);
|
||||
@@ -345,8 +347,10 @@ mi_new_thread (struct thread_info *t)
|
||||
}
|
||||
}
|
||||
|
||||
/* Observer for the thread_exit notification. */
|
||||
|
||||
static void
|
||||
mi_thread_exit (struct thread_info *t, int silent)
|
||||
mi_thread_exit (thread_info *t, gdb::optional<ULONGEST> exit_code, bool silent)
|
||||
{
|
||||
SWITCH_THRU_ALL_UIS ()
|
||||
{
|
||||
|
||||
@@ -625,11 +625,6 @@ nbsd_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
|
||||
{
|
||||
/* NetBSD does not store an LWP exit status. */
|
||||
ourstatus->set_thread_exited (0);
|
||||
|
||||
if (print_thread_events)
|
||||
gdb_printf (_("[%s exited]\n"),
|
||||
target_pid_to_str (wptid).c_str ());
|
||||
delete_thread (thr);
|
||||
}
|
||||
|
||||
/* The GDB core expects that the rest of the threads are running. */
|
||||
|
||||
@@ -126,10 +126,13 @@ extern observable<struct objfile */* objfile */> free_objfile;
|
||||
/* The thread specified by T has been created. */
|
||||
extern observable<struct thread_info */* t */> new_thread;
|
||||
|
||||
/* The thread specified by T has exited. The SILENT argument
|
||||
indicates that gdb is removing the thread from its tables without
|
||||
wanting to notify the user about it. */
|
||||
extern observable<struct thread_info */* t */, int /* silent */> thread_exit;
|
||||
/* The thread specified by T has exited. EXIT_CODE is the thread's
|
||||
exit code, if available. The SILENT argument indicates that GDB is
|
||||
removing the thread from its tables without wanting to notify the
|
||||
CLI about it. */
|
||||
extern observable<thread_info */* t */,
|
||||
gdb::optional<ULONGEST> /* exit_code */,
|
||||
bool /* silent */> thread_exit;
|
||||
|
||||
/* An explicit stop request was issued to PTID. If PTID equals
|
||||
minus_one_ptid, the request applied to all threads. If
|
||||
|
||||
@@ -2115,9 +2115,6 @@ wait_again:
|
||||
case PR_SYSENTRY:
|
||||
if (what == SYS_lwp_exit)
|
||||
{
|
||||
if (print_thread_events)
|
||||
gdb_printf (_("[%s exited]\n"),
|
||||
target_pid_to_str (retval).c_str ());
|
||||
delete_thread (find_thread_ptid (this, retval));
|
||||
target_continue_no_signal (ptid);
|
||||
goto wait_again;
|
||||
@@ -2222,9 +2219,6 @@ wait_again:
|
||||
}
|
||||
else if (what == SYS_lwp_exit)
|
||||
{
|
||||
if (print_thread_events)
|
||||
gdb_printf (_("[%s exited]\n"),
|
||||
target_pid_to_str (retval).c_str ());
|
||||
delete_thread (find_thread_ptid (this, retval));
|
||||
status->set_spurious ();
|
||||
return retval;
|
||||
|
||||
@@ -360,7 +360,9 @@ add_thread_object (struct thread_info *tp)
|
||||
}
|
||||
|
||||
static void
|
||||
delete_thread_object (struct thread_info *tp, int ignore)
|
||||
delete_thread_object (thread_info *tp,
|
||||
gdb::optional<ULONGEST> /* exit_code */,
|
||||
bool /* silent */)
|
||||
{
|
||||
if (!gdb_python_initialized)
|
||||
return;
|
||||
|
||||
303
gdb/remote.c
303
gdb/remote.c
@@ -248,6 +248,9 @@ enum {
|
||||
/* Support for the QThreadEvents packet. */
|
||||
PACKET_QThreadEvents,
|
||||
|
||||
/* Support for the QThreadOptions packet. */
|
||||
PACKET_QThreadOptions,
|
||||
|
||||
/* Support for multi-process extensions. */
|
||||
PACKET_multiprocess_feature,
|
||||
|
||||
@@ -489,6 +492,10 @@ public: /* data */
|
||||
the target know about program signals list changes. */
|
||||
char *last_program_signals_packet = nullptr;
|
||||
|
||||
/* Similarly, the last QThreadEvents state we sent to the
|
||||
target. */
|
||||
bool last_thread_events = false;
|
||||
|
||||
gdb_signal last_sent_signal = GDB_SIGNAL_0;
|
||||
|
||||
bool last_sent_step = false;
|
||||
@@ -556,6 +563,10 @@ public: /* data */
|
||||
this can go away. */
|
||||
int wait_forever_enabled_p = 1;
|
||||
|
||||
/* The set of thread options the target reported it supports, via
|
||||
qSupported. */
|
||||
gdb_thread_options supported_thread_options = 0;
|
||||
|
||||
private:
|
||||
/* Mapping of remote protocol data for each gdbarch. Usually there
|
||||
is only one entry here, though we may see more with stubs that
|
||||
@@ -716,6 +727,8 @@ public:
|
||||
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;
|
||||
@@ -842,6 +855,8 @@ public:
|
||||
|
||||
void thread_events (int) override;
|
||||
|
||||
bool supports_set_thread_options (gdb_thread_options) override;
|
||||
|
||||
int can_do_single_step () override;
|
||||
|
||||
void terminal_inferior () override;
|
||||
@@ -979,6 +994,7 @@ public:
|
||||
const struct btrace_config *btrace_conf (const struct btrace_target_info *) override;
|
||||
bool augmented_libraries_svr4_read () override;
|
||||
void follow_fork (inferior *, ptid_t, target_waitkind, bool, bool) override;
|
||||
void follow_clone (ptid_t child_ptid) override;
|
||||
void follow_exec (inferior *, ptid_t, const char *) override;
|
||||
int insert_fork_catchpoint (int) override;
|
||||
int remove_fork_catchpoint (int) override;
|
||||
@@ -1072,7 +1088,7 @@ public: /* Remote specific methods. */
|
||||
|
||||
void remote_btrace_maybe_reopen ();
|
||||
|
||||
void remove_new_fork_children (threads_listing_context *context);
|
||||
void remove_new_children (threads_listing_context *context);
|
||||
void kill_new_fork_children (inferior *inf);
|
||||
void discard_pending_stop_replies (struct inferior *inf);
|
||||
int stop_reply_queue_length ();
|
||||
@@ -1139,6 +1155,9 @@ public: /* Remote specific methods. */
|
||||
|
||||
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 ();
|
||||
|
||||
@@ -2755,9 +2774,10 @@ remote_target::remote_add_thread (ptid_t ptid, bool running, bool executing,
|
||||
else
|
||||
thread = add_thread (this, ptid);
|
||||
|
||||
/* We start by assuming threads are resumed. That state then gets updated
|
||||
when we process a matching stop reply. */
|
||||
get_remote_thread_info (thread)->set_resumed ();
|
||||
/* We start by assuming threads are resumed. That state then gets
|
||||
updated when we process a matching stop reply. */
|
||||
if (executing)
|
||||
get_remote_thread_info (thread)->set_resumed ();
|
||||
|
||||
set_executing (this, ptid, executing);
|
||||
set_running (this, ptid, running);
|
||||
@@ -4152,15 +4172,25 @@ remote_target::update_thread_list ()
|
||||
if (has_single_non_exited_thread (tp->inf))
|
||||
continue;
|
||||
|
||||
/* Do not remove the thread if we've requested to be
|
||||
notified of its exit. For example, the thread may be
|
||||
displaced stepping, infrun will need to handle the
|
||||
exit event, and displaced stepping info is recorded
|
||||
in the thread object. If we deleted the thread now,
|
||||
we'd lose that info. */
|
||||
if ((tp->thread_options () & GDB_THREAD_OPTION_EXIT) != 0)
|
||||
continue;
|
||||
|
||||
/* Not found. */
|
||||
delete_thread (tp);
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove any unreported fork child threads from CONTEXT so
|
||||
that we don't interfere with follow fork, which is where
|
||||
creation of such threads is handled. */
|
||||
remove_new_fork_children (&context);
|
||||
/* Remove any unreported fork/vfork/clone child threads from
|
||||
CONTEXT so that we don't interfere with follow
|
||||
fork/vfork/clone, which is where creation of such threads is
|
||||
handled. */
|
||||
remove_new_children (&context);
|
||||
|
||||
/* And now add threads we don't know about yet to our list. */
|
||||
for (thread_item &item : context.items)
|
||||
@@ -5124,6 +5154,8 @@ remote_target::start_remote_1 (int from_tty, int extended_p)
|
||||
}
|
||||
else
|
||||
switch_to_thread (find_thread_ptid (this, curr_thread));
|
||||
|
||||
get_remote_thread_info (inferior_thread ())->set_resumed ();
|
||||
}
|
||||
|
||||
/* init_wait_for_inferior should be called before get_offsets in order
|
||||
@@ -5462,7 +5494,8 @@ remote_supported_packet (remote_target *remote,
|
||||
|
||||
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 ();
|
||||
|
||||
@@ -5499,6 +5532,49 @@ remote_packet_size (remote_target *remote, const protocol_feature *feature,
|
||||
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,
|
||||
@@ -5601,6 +5677,8 @@ static const struct protocol_feature remote_protocol_features[] = {
|
||||
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 },
|
||||
@@ -5703,6 +5781,10 @@ remote_target::remote_query_supported ()
|
||||
!= 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+");
|
||||
@@ -6083,16 +6165,25 @@ is_fork_status (target_waitkind kind)
|
||||
|| kind == TARGET_WAITKIND_VFORKED);
|
||||
}
|
||||
|
||||
/* Return THREAD's pending status if it is a pending fork parent, else
|
||||
return nullptr. */
|
||||
/* Return a reference to the field where a pending child status, if
|
||||
there's one, is recorded. If there's no child event pending, the
|
||||
returned waitstatus has TARGET_WAITKIND_IGNORE kind. */
|
||||
|
||||
static const target_waitstatus &
|
||||
thread_pending_status (struct thread_info *thread)
|
||||
{
|
||||
return (thread->has_pending_waitstatus ()
|
||||
? thread->pending_waitstatus ()
|
||||
: thread->pending_follow);
|
||||
}
|
||||
|
||||
/* Return THREAD's pending status if it is a pending fork/vfork (but
|
||||
not clone) parent, else return nullptr. */
|
||||
|
||||
static const target_waitstatus *
|
||||
thread_pending_fork_status (struct thread_info *thread)
|
||||
{
|
||||
const target_waitstatus &ws
|
||||
= (thread->has_pending_waitstatus ()
|
||||
? thread->pending_waitstatus ()
|
||||
: thread->pending_follow);
|
||||
const target_waitstatus &ws = thread_pending_status (thread);
|
||||
|
||||
if (!is_fork_status (ws.kind ()))
|
||||
return nullptr;
|
||||
@@ -6100,6 +6191,20 @@ thread_pending_fork_status (struct thread_info *thread)
|
||||
return &ws;
|
||||
}
|
||||
|
||||
/* Return THREAD's pending status if is is a pending fork/vfork/clone
|
||||
event, else return nullptr. */
|
||||
|
||||
static const target_waitstatus *
|
||||
thread_pending_child_status (thread_info *thread)
|
||||
{
|
||||
const target_waitstatus &ws = thread_pending_status (thread);
|
||||
|
||||
if (!is_new_child_status (ws.kind ()))
|
||||
return nullptr;
|
||||
|
||||
return &ws;
|
||||
}
|
||||
|
||||
/* Detach the specified process. */
|
||||
|
||||
void
|
||||
@@ -6265,6 +6370,12 @@ remote_target::follow_fork (inferior *child_inf, ptid_t child_ptid,
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
remote_target::follow_clone (ptid_t child_ptid)
|
||||
{
|
||||
remote_add_thread (child_ptid, false, false, false);
|
||||
}
|
||||
|
||||
/* Target follow-exec function for remote targets. Save EXECD_PATHNAME
|
||||
in the program space of the new inferior. */
|
||||
|
||||
@@ -6746,6 +6857,8 @@ remote_target::resume (ptid_t scope_ptid, int step, enum gdb_signal siggnal)
|
||||
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
|
||||
@@ -6908,6 +7021,8 @@ remote_target::commit_resumed ()
|
||||
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
|
||||
@@ -6992,10 +7107,10 @@ remote_target::commit_resumed ()
|
||||
if (priv->get_resume_state () == resume_state::RESUMED_PENDING_VCONT)
|
||||
any_pending_vcont_resume = true;
|
||||
|
||||
/* If a thread is the parent of an unfollowed fork, then we
|
||||
can't do a global wildcard, as that would resume the fork
|
||||
child. */
|
||||
if (thread_pending_fork_status (tp) != nullptr)
|
||||
/* If a thread is the parent of an unfollowed fork/vfork/clone,
|
||||
then we can't do a global wildcard, as that would resume the
|
||||
pending child. */
|
||||
if (thread_pending_child_status (tp) != nullptr)
|
||||
may_global_wildcard_vcont = false;
|
||||
}
|
||||
|
||||
@@ -7455,22 +7570,22 @@ const notif_client notif_client_stop =
|
||||
REMOTE_NOTIF_STOP,
|
||||
};
|
||||
|
||||
/* If CONTEXT contains any fork child threads that have not been
|
||||
reported yet, remove them from the CONTEXT list. If such a
|
||||
thread exists it is because we are stopped at a fork catchpoint
|
||||
and have not yet called follow_fork, which will set up the
|
||||
host-side data structures for the new process. */
|
||||
/* If CONTEXT contains any fork/vfork/clone child threads that have
|
||||
not been reported yet, remove them from the CONTEXT list. If such
|
||||
a thread exists it is because we are stopped at a fork/vfork/clone
|
||||
catchpoint and have not yet called follow_fork/follow_clone, which
|
||||
will set up the host-side data structures for the new child. */
|
||||
|
||||
void
|
||||
remote_target::remove_new_fork_children (threads_listing_context *context)
|
||||
remote_target::remove_new_children (threads_listing_context *context)
|
||||
{
|
||||
const notif_client *notif = ¬if_client_stop;
|
||||
|
||||
/* For any threads stopped at a fork event, remove the corresponding
|
||||
fork child threads from the CONTEXT list. */
|
||||
/* For any threads stopped at a (v)fork/clone event, remove the
|
||||
corresponding child threads from the CONTEXT list. */
|
||||
for (thread_info *thread : all_non_exited_threads (this))
|
||||
{
|
||||
const target_waitstatus *ws = thread_pending_fork_status (thread);
|
||||
const target_waitstatus *ws = thread_pending_child_status (thread);
|
||||
|
||||
if (ws == nullptr)
|
||||
continue;
|
||||
@@ -7478,13 +7593,12 @@ remote_target::remove_new_fork_children (threads_listing_context *context)
|
||||
context->remove_thread (ws->child_ptid ());
|
||||
}
|
||||
|
||||
/* Check for any pending fork events (not reported or processed yet)
|
||||
in process PID and remove those fork child threads from the
|
||||
CONTEXT list as well. */
|
||||
/* Check for any pending (v)fork/clone events (not reported or
|
||||
processed yet) in process PID and remove those child threads from
|
||||
the CONTEXT list as well. */
|
||||
remote_notif_get_pending_events (notif);
|
||||
for (auto &event : get_remote_state ()->stop_reply_queue)
|
||||
if (event->ws.kind () == TARGET_WAITKIND_FORKED
|
||||
|| event->ws.kind () == TARGET_WAITKIND_VFORKED)
|
||||
if (is_new_child_status (event->ws.kind ()))
|
||||
context->remove_thread (event->ws.child_ptid ());
|
||||
else if (event->ws.kind () == TARGET_WAITKIND_THREAD_EXITED)
|
||||
context->remove_thread (event->ptid);
|
||||
@@ -7815,6 +7929,8 @@ Packet: '%s'\n"),
|
||||
event->ws.set_forked (read_ptid (++p1, &p));
|
||||
else if (strprefix (p, p1, "vfork"))
|
||||
event->ws.set_vforked (read_ptid (++p1, &p));
|
||||
else if (strprefix (p, p1, "clone"))
|
||||
event->ws.set_thread_cloned (read_ptid (++p1, &p));
|
||||
else if (strprefix (p, p1, "vforkdone"))
|
||||
{
|
||||
event->ws.set_vfork_done ();
|
||||
@@ -8247,7 +8363,8 @@ remote_target::process_stop_reply (struct stop_reply *stop_reply,
|
||||
&& status->kind () != TARGET_WAITKIND_NO_RESUMED)
|
||||
{
|
||||
/* Expedited registers. */
|
||||
if (!stop_reply->regcache.empty ())
|
||||
if (status->kind () != TARGET_WAITKIND_THREAD_EXITED
|
||||
&& !stop_reply->regcache.empty ())
|
||||
{
|
||||
struct regcache *regcache
|
||||
= get_thread_arch_regcache (this, ptid, stop_reply->arch);
|
||||
@@ -8433,7 +8550,7 @@ remote_target::wait_as (ptid_t ptid, target_waitstatus *status,
|
||||
again. Keep waiting for events. */
|
||||
rs->waiting_for_stop_reply = 1;
|
||||
break;
|
||||
case 'N': case 'T': case 'S': case 'X': case 'W':
|
||||
case 'N': case 'T': case 'S': case 'X': case 'W': case 'w':
|
||||
{
|
||||
/* There is a stop reply to handle. */
|
||||
rs->waiting_for_stop_reply = 0;
|
||||
@@ -14645,6 +14762,9 @@ remote_target::thread_events (int enable)
|
||||
if (m_features.packet_support (PACKET_QThreadEvents) == PACKET_DISABLE)
|
||||
return;
|
||||
|
||||
if (rs->last_thread_events == enable)
|
||||
return;
|
||||
|
||||
xsnprintf (rs->buf.data (), size, "QThreadEvents:%x", enable ? 1 : 0);
|
||||
putpkt (rs->buf);
|
||||
getpkt (&rs->buf, 0);
|
||||
@@ -14654,6 +14774,7 @@ remote_target::thread_events (int enable)
|
||||
case PACKET_OK:
|
||||
if (strcmp (rs->buf.data (), "OK") != 0)
|
||||
error (_("Remote refused setting thread events: %s"), rs->buf.data ());
|
||||
rs->last_thread_events = enable;
|
||||
break;
|
||||
case PACKET_ERROR:
|
||||
warning (_("Remote failure reply: %s"), rs->buf.data ());
|
||||
@@ -14663,6 +14784,115 @@ remote_target::thread_events (int enable)
|
||||
}
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
@@ -15403,6 +15633,9 @@ Show the maximum size of the address (in bits) in a memory packet."), NULL,
|
||||
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);
|
||||
|
||||
|
||||
@@ -1088,13 +1088,13 @@ ppc_displaced_step_prepare (gdbarch *arch, thread_info *thread,
|
||||
|
||||
static displaced_step_finish_status
|
||||
ppc_displaced_step_finish (gdbarch *arch, thread_info *thread,
|
||||
gdb_signal sig)
|
||||
const target_waitstatus &status)
|
||||
{
|
||||
ppc_inferior_data *per_inferior = get_ppc_per_inferior (thread->inf);
|
||||
|
||||
gdb_assert (per_inferior->disp_step_buf.has_value ());
|
||||
|
||||
return per_inferior->disp_step_buf->finish (arch, thread, sig);
|
||||
return per_inferior->disp_step_buf->finish (arch, thread, status);
|
||||
}
|
||||
|
||||
/* Implementation of gdbarch_displaced_step_restore_all_in_ptid. */
|
||||
|
||||
@@ -176,6 +176,8 @@
|
||||
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_struct_target_waitstatus_p (struct target_waitstatus *status)
|
||||
|
||||
@@ -76,6 +76,7 @@ struct dummy_target : public target_ops
|
||||
int insert_vfork_catchpoint (int arg0) override;
|
||||
int remove_vfork_catchpoint (int arg0) override;
|
||||
void follow_fork (inferior *arg0, ptid_t arg1, target_waitkind arg2, bool arg3, bool arg4) override;
|
||||
void follow_clone (ptid_t arg0) override;
|
||||
int insert_exec_catchpoint (int arg0) override;
|
||||
int remove_exec_catchpoint (int arg0) override;
|
||||
void follow_exec (inferior *arg0, ptid_t arg1, const char *arg2) override;
|
||||
@@ -105,6 +106,7 @@ struct dummy_target : public target_ops
|
||||
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;
|
||||
@@ -250,6 +252,7 @@ struct debug_target : public target_ops
|
||||
int insert_vfork_catchpoint (int arg0) override;
|
||||
int remove_vfork_catchpoint (int arg0) override;
|
||||
void follow_fork (inferior *arg0, ptid_t arg1, target_waitkind arg2, bool arg3, bool arg4) override;
|
||||
void follow_clone (ptid_t arg0) override;
|
||||
int insert_exec_catchpoint (int arg0) override;
|
||||
int remove_exec_catchpoint (int arg0) override;
|
||||
void follow_exec (inferior *arg0, ptid_t arg1, const char *arg2) override;
|
||||
@@ -279,6 +282,7 @@ struct debug_target : public target_ops
|
||||
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;
|
||||
@@ -1545,6 +1549,28 @@ debug_target::follow_fork (inferior *arg0, ptid_t arg1, target_waitkind arg2, bo
|
||||
gdb_puts (")\n", gdb_stdlog);
|
||||
}
|
||||
|
||||
void
|
||||
target_ops::follow_clone (ptid_t arg0)
|
||||
{
|
||||
this->beneath ()->follow_clone (arg0);
|
||||
}
|
||||
|
||||
void
|
||||
dummy_target::follow_clone (ptid_t arg0)
|
||||
{
|
||||
default_follow_clone (this, arg0);
|
||||
}
|
||||
|
||||
void
|
||||
debug_target::follow_clone (ptid_t arg0)
|
||||
{
|
||||
gdb_printf (gdb_stdlog, "-> %s->follow_clone (...)\n", this->beneath ()->shortname ());
|
||||
this->beneath ()->follow_clone (arg0);
|
||||
gdb_printf (gdb_stdlog, "<- %s->follow_clone (", this->beneath ()->shortname ());
|
||||
target_debug_print_ptid_t (arg0);
|
||||
gdb_puts (")\n", gdb_stdlog);
|
||||
}
|
||||
|
||||
int
|
||||
target_ops::insert_exec_catchpoint (int arg0)
|
||||
{
|
||||
@@ -2248,6 +2274,32 @@ debug_target::thread_events (int arg0)
|
||||
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 ()
|
||||
{
|
||||
|
||||
16
gdb/target.c
16
gdb/target.c
@@ -2701,6 +2701,13 @@ default_follow_fork (struct target_ops *self, inferior *child_inf,
|
||||
internal_error (_("could not find a target to follow fork"));
|
||||
}
|
||||
|
||||
static void
|
||||
default_follow_clone (struct target_ops *self, ptid_t child_ptid)
|
||||
{
|
||||
/* Some target returned a clone event, but did not know how to follow it. */
|
||||
internal_error (_("could not find a target to follow clone"));
|
||||
}
|
||||
|
||||
/* See target.h. */
|
||||
|
||||
void
|
||||
@@ -4351,6 +4358,15 @@ target_thread_events (int enable)
|
||||
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;
|
||||
|
||||
15
gdb/target.h
15
gdb/target.h
@@ -637,6 +637,13 @@ struct target_ops
|
||||
TARGET_DEFAULT_RETURN (1);
|
||||
virtual void follow_fork (inferior *, ptid_t, target_waitkind, bool, bool)
|
||||
TARGET_DEFAULT_FUNC (default_follow_fork);
|
||||
|
||||
/* Add CHILD_PTID to the thread list, after handling a
|
||||
TARGET_WAITKIND_THREAD_CLONE event for the clone parent. The
|
||||
parent is inferior_ptid. */
|
||||
virtual void follow_clone (ptid_t child_ptid)
|
||||
TARGET_DEFAULT_FUNC (default_follow_clone);
|
||||
|
||||
virtual int insert_exec_catchpoint (int)
|
||||
TARGET_DEFAULT_RETURN (1);
|
||||
virtual int remove_exec_catchpoint (int)
|
||||
@@ -729,6 +736,10 @@ struct target_ops
|
||||
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 ()
|
||||
@@ -1888,6 +1899,10 @@ extern void target_async (bool enable);
|
||||
/* 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;
|
||||
|
||||
@@ -188,3 +188,15 @@ target_read_string (CORE_ADDR memaddr, int len, int *bytes_read)
|
||||
|
||||
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),
|
||||
MAP_ENUM_FLAG (GDB_THREAD_OPTION_EXIT),
|
||||
};
|
||||
return options.to_string (mapping);
|
||||
}
|
||||
|
||||
@@ -22,9 +22,29 @@
|
||||
|
||||
#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,
|
||||
|
||||
/* Tell the target to report TARGET_WAITKIND_THREAD_EXIT events for
|
||||
the thread. */
|
||||
GDB_THREAD_OPTION_EXIT = 1 << 1,
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
@@ -45,6 +45,7 @@ DIAGNOSTIC_ERROR_SWITCH
|
||||
|
||||
case TARGET_WAITKIND_FORKED:
|
||||
case TARGET_WAITKIND_VFORKED:
|
||||
case TARGET_WAITKIND_THREAD_CLONED:
|
||||
return string_appendf (str, ", child_ptid = %s",
|
||||
this->child_ptid ().to_string ().c_str ());
|
||||
|
||||
|
||||
@@ -95,6 +95,13 @@ enum target_waitkind
|
||||
/* There are no resumed children left in the program. */
|
||||
TARGET_WAITKIND_NO_RESUMED,
|
||||
|
||||
/* The thread was cloned. The event's ptid corresponds to the
|
||||
cloned parent. The cloned child is held stopped at its entry
|
||||
point, and its ptid is in the event's m_child_ptid. The target
|
||||
must not add the cloned child to GDB's thread list until
|
||||
target_ops::follow_clone() is called. */
|
||||
TARGET_WAITKIND_THREAD_CLONED,
|
||||
|
||||
/* The thread was created. */
|
||||
TARGET_WAITKIND_THREAD_CREATED,
|
||||
|
||||
@@ -102,6 +109,17 @@ enum target_waitkind
|
||||
TARGET_WAITKIND_THREAD_EXITED,
|
||||
};
|
||||
|
||||
/* Determine if KIND represents an event with a new child - a fork,
|
||||
vfork, or clone. */
|
||||
|
||||
static inline bool
|
||||
is_new_child_status (target_waitkind kind)
|
||||
{
|
||||
return (kind == TARGET_WAITKIND_FORKED
|
||||
|| kind == TARGET_WAITKIND_VFORKED
|
||||
|| kind == TARGET_WAITKIND_THREAD_CLONED);
|
||||
}
|
||||
|
||||
/* Return KIND as a string. */
|
||||
|
||||
static inline const char *
|
||||
@@ -125,6 +143,8 @@ DIAGNOSTIC_ERROR_SWITCH
|
||||
return "FORKED";
|
||||
case TARGET_WAITKIND_VFORKED:
|
||||
return "VFORKED";
|
||||
case TARGET_WAITKIND_THREAD_CLONED:
|
||||
return "THREAD_CLONED";
|
||||
case TARGET_WAITKIND_EXECD:
|
||||
return "EXECD";
|
||||
case TARGET_WAITKIND_VFORK_DONE:
|
||||
@@ -325,6 +345,14 @@ struct target_waitstatus
|
||||
return *this;
|
||||
}
|
||||
|
||||
target_waitstatus &set_thread_cloned (ptid_t child_ptid)
|
||||
{
|
||||
this->reset ();
|
||||
m_kind = TARGET_WAITKIND_THREAD_CLONED;
|
||||
m_value.child_ptid = child_ptid;
|
||||
return *this;
|
||||
}
|
||||
|
||||
target_waitstatus &set_thread_created ()
|
||||
{
|
||||
this->reset ();
|
||||
@@ -369,8 +397,7 @@ struct target_waitstatus
|
||||
|
||||
ptid_t child_ptid () const
|
||||
{
|
||||
gdb_assert (m_kind == TARGET_WAITKIND_FORKED
|
||||
|| m_kind == TARGET_WAITKIND_VFORKED);
|
||||
gdb_assert (is_new_child_status (m_kind));
|
||||
return m_value.child_ptid;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,44 +42,15 @@ if { [istarget "i\[34567\]86-*-linux*"] || [istarget "x86_64-*-linux*"] } {
|
||||
}
|
||||
|
||||
proc_with_prefix check_pc_after_cross_syscall { displaced syscall syscall_insn_next_addr } {
|
||||
global gdb_prompt
|
||||
|
||||
set syscall_insn_next_addr_found [get_hexadecimal_valueof "\$pc" "0"]
|
||||
|
||||
# After the 'stepi' we expect thread 1 to still be selected.
|
||||
# However, when displaced stepping over a clone bug gdb/19675
|
||||
# means this might not be the case.
|
||||
#
|
||||
# Which thread we end up in depends on a race between the original
|
||||
# thread-1, and the new thread (created by the clone), so we can't
|
||||
# guarantee which thread we will be in at this point.
|
||||
#
|
||||
# For the fork/vfork syscalls, which are correctly handled by
|
||||
# displaced stepping we will always be in thread-1 or the original
|
||||
# process at this point.
|
||||
set curr_thread "unknown"
|
||||
gdb_test_multiple "info threads" "" {
|
||||
-re "Id\\s+Target Id\\s+Frame\\s*\r\n" {
|
||||
exp_continue
|
||||
}
|
||||
-re "^\\* (\\d+)\\s+\[^\r\n\]+\r\n" {
|
||||
gdb_test_multiple "thread" "" {
|
||||
-re -wrap "Current thread is (\\d+) .*" {
|
||||
set curr_thread $expect_out(1,string)
|
||||
exp_continue
|
||||
pass $gdb_test_name
|
||||
}
|
||||
-re "^\\s+\\d+\\s+\[^\r\n\]+\r\n" {
|
||||
exp_continue
|
||||
}
|
||||
-re "$gdb_prompt " {
|
||||
}
|
||||
}
|
||||
|
||||
# If we are displaced stepping over a clone, and we ended up in
|
||||
# the wrong thread then the following check of the $pc value will
|
||||
# fail.
|
||||
if { $displaced == "on" && $syscall == "clone" && $curr_thread != 1 } {
|
||||
# GDB doesn't support stepping over clone syscall with
|
||||
# displaced stepping.
|
||||
setup_kfail "*-*-*" "gdb/19675"
|
||||
}
|
||||
|
||||
gdb_assert {$syscall_insn_next_addr != 0 \
|
||||
@@ -299,15 +270,6 @@ proc step_over_syscall { syscall } {
|
||||
|
||||
gdb_test "break marker" "Breakpoint.*at.* file .*${testfile}.c, line.*"
|
||||
|
||||
# If we are displaced stepping over a clone syscall then
|
||||
# we expect the following check to fail. See also the
|
||||
# code in check_pc_after_cross_syscall.
|
||||
if { $displaced == "on" && $syscall == "clone" } {
|
||||
# GDB doesn't support stepping over clone syscall with
|
||||
# displaced stepping.
|
||||
setup_kfail "*-*-*" "gdb/19675"
|
||||
}
|
||||
|
||||
gdb_test "continue" "Continuing\\..*Breakpoint \[0-9\]+, marker \\(\\) at.*" \
|
||||
"continue to marker ($syscall)"
|
||||
}
|
||||
|
||||
@@ -233,19 +233,11 @@ foreach_mi_ui_mode mode {
|
||||
exp_continue
|
||||
}
|
||||
|
||||
# The output has arrived! Check how we did. There are other bugs
|
||||
# that come into play here which change what output we'll see.
|
||||
if { $saw_mi_thread_exited && $saw_mi_bp_deleted \
|
||||
&& $saw_cli_thread_exited \
|
||||
&& $saw_cli_bp_deleted } {
|
||||
kpass "gdb/30129" $gdb_test_name
|
||||
} elseif { $saw_mi_thread_exited && $saw_mi_bp_deleted \
|
||||
&& !$saw_cli_thread_exited \
|
||||
&& $saw_cli_bp_deleted } {
|
||||
kfail "gdb/30129" $gdb_test_name
|
||||
} else {
|
||||
fail "$gdb_test_name"
|
||||
}
|
||||
# The output has arrived! Check how we did.
|
||||
gdb_assert { $saw_mi_thread_exited && $saw_mi_bp_deleted \
|
||||
&& $saw_cli_thread_exited \
|
||||
&& $saw_cli_bp_deleted } \
|
||||
$gdb_test_name
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
54
gdb/testsuite/gdb.threads/schedlock-new-thread.c
Normal file
54
gdb/testsuite/gdb.threads/schedlock-new-thread.c
Normal file
@@ -0,0 +1,54 @@
|
||||
/* This testcase is part of GDB, the GNU debugger.
|
||||
|
||||
Copyright 2021-2022 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 <pthread.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void *
|
||||
thread_func (void *arg)
|
||||
{
|
||||
#if !SCHEDLOCK
|
||||
while (1)
|
||||
sleep (1);
|
||||
#endif
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
pthread_t thread;
|
||||
int ret;
|
||||
|
||||
ret = pthread_create (&thread, NULL, thread_func, NULL); /* set break 1 here */
|
||||
assert (ret == 0);
|
||||
|
||||
#if SCHEDLOCK
|
||||
/* When testing with schedlock enabled, the new thread won't run, so
|
||||
we can't join it, as that would hang forever. Instead, sleep for
|
||||
a bit, enough that if the spawned thread is scheduled, it hits
|
||||
the thread_func breakpoint before the main thread reaches the
|
||||
"return 0" line below. */
|
||||
sleep (3);
|
||||
#else
|
||||
pthread_join (thread, NULL);
|
||||
#endif
|
||||
|
||||
return 0; /* set break 2 here */
|
||||
}
|
||||
67
gdb/testsuite/gdb.threads/schedlock-new-thread.exp
Normal file
67
gdb/testsuite/gdb.threads/schedlock-new-thread.exp
Normal file
@@ -0,0 +1,67 @@
|
||||
# Copyright 2021-2022 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 continuing over a thread spawn with scheduler-locking on.
|
||||
|
||||
standard_testfile .c
|
||||
|
||||
foreach_with_prefix schedlock {off on} {
|
||||
set sl [expr $schedlock == "on" ? 1 : 0]
|
||||
if { [build_executable "failed to prepare" $testfile-$sl \
|
||||
$srcfile \
|
||||
[list debug pthreads additional_flags=-DSCHEDLOCK=$sl]] \
|
||||
== -1 } {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
proc test {non-stop schedlock} {
|
||||
save_vars ::GDBFLAGS {
|
||||
append ::GDBFLAGS " -ex \"set non-stop ${non-stop}\""
|
||||
set sl [expr $schedlock == "on" ? 1 : 0]
|
||||
clean_restart $::binfile-$sl
|
||||
}
|
||||
|
||||
set linenum1 [gdb_get_line_number "set break 1 here"]
|
||||
|
||||
if { ![runto $::srcfile:$linenum1] } {
|
||||
return
|
||||
}
|
||||
|
||||
delete_breakpoints
|
||||
|
||||
set linenum2 [gdb_get_line_number "set break 2 here"]
|
||||
gdb_breakpoint $linenum2
|
||||
|
||||
gdb_breakpoint "thread_func"
|
||||
|
||||
gdb_test_no_output "set scheduler-locking $schedlock"
|
||||
|
||||
if {$schedlock} {
|
||||
gdb_test "continue" \
|
||||
"return 0.*set break 2 here .*" \
|
||||
"continue does not stop in new thread"
|
||||
} else {
|
||||
gdb_test "continue" \
|
||||
"thread_func .*" \
|
||||
"continue stops in new thread"
|
||||
}
|
||||
}
|
||||
|
||||
foreach_with_prefix non-stop {off on} {
|
||||
foreach_with_prefix schedlock {off on} {
|
||||
test ${non-stop} ${schedlock}
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,12 @@ proc do_test { execr_thread different_text_segments displaced_stepping } {
|
||||
gdb_breakpoint foo
|
||||
gdb_test "continue" "Breakpoint $decimal, foo .*" \
|
||||
"continue to foo"
|
||||
|
||||
# Test that GDB is able to kill the inferior. This may fail if
|
||||
# e.g., GDB does not dispose of the pre-exec threads properly.
|
||||
gdb_test "with confirm off -- kill" \
|
||||
"\\\[Inferior 1 (.*) killed\\\]" \
|
||||
"kill inferior"
|
||||
}
|
||||
|
||||
foreach_with_prefix displaced_stepping {auto off} {
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/* This testcase is part of GDB, the GNU debugger.
|
||||
|
||||
Copyright 2021-2022 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 <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include "../lib/my-syscalls.h"
|
||||
|
||||
#define NUM_THREADS 32
|
||||
|
||||
static void *
|
||||
stepper_over_exit_thread (void *v)
|
||||
{
|
||||
my_exit (0);
|
||||
|
||||
/* my_exit above should exit the thread, we don't expect to reach
|
||||
here. */
|
||||
abort ();
|
||||
}
|
||||
|
||||
static void *
|
||||
spawner_thread (void *v)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
pthread_t threads[NUM_THREADS];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NUM_THREADS; i++)
|
||||
pthread_create (&threads[i], NULL, stepper_over_exit_thread, NULL);
|
||||
|
||||
for (i = 0; i < NUM_THREADS; i++)
|
||||
pthread_join (threads[i], NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
break_here (void)
|
||||
{
|
||||
}
|
||||
|
||||
static void *
|
||||
breakpoint_hitter_thread (void *v)
|
||||
{
|
||||
for (;;)
|
||||
break_here ();
|
||||
}
|
||||
|
||||
int
|
||||
main ()
|
||||
{
|
||||
pthread_t breakpoint_hitter;
|
||||
pthread_t spawner;
|
||||
|
||||
alarm (60);
|
||||
|
||||
pthread_create (&spawner, NULL, spawner_thread, NULL);
|
||||
pthread_create (&breakpoint_hitter, NULL, breakpoint_hitter_thread, NULL);
|
||||
|
||||
pthread_join (spawner, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
# Copyright 2021-2022 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 stepping over a breakpoint installed on an instruction that
|
||||
# exits the thread, while another thread is repeatedly hitting a
|
||||
# breakpoint, causing GDB to stop all threads.
|
||||
|
||||
standard_testfile .c
|
||||
|
||||
set syscalls_src $srcdir/lib/my-syscalls.S
|
||||
|
||||
if { [build_executable "failed to prepare" $testfile \
|
||||
[list $srcfile $syscalls_src] {debug pthreads}] == -1 } {
|
||||
return
|
||||
}
|
||||
|
||||
proc test {displaced-stepping target-non-stop} {
|
||||
save_vars ::GDBFLAGS {
|
||||
append ::GDBFLAGS " -ex \"maintenance set target-non-stop ${target-non-stop}\""
|
||||
clean_restart $::binfile
|
||||
}
|
||||
|
||||
gdb_test_no_output "set displaced-stepping ${displaced-stepping}"
|
||||
|
||||
if { ![runto_main] } {
|
||||
return
|
||||
}
|
||||
|
||||
# The "stepper over exit" threads will step over an instruction
|
||||
# that causes them to exit.
|
||||
gdb_test "break my_exit_syscall if 0"
|
||||
|
||||
# The "breakpoint hitter" thread will repeatedly hit this
|
||||
# breakpoint, causing GDB to stop all threads.
|
||||
gdb_test "break break_here"
|
||||
|
||||
# To avoid flooding the log with thread created/exited messages.
|
||||
gdb_test_no_output "set print thread-events off"
|
||||
|
||||
# Make sure the target reports the breakpoint stops.
|
||||
gdb_test_no_output "set breakpoint condition-evaluation host"
|
||||
|
||||
for { set i 0 } { $i < 30 } { incr i } {
|
||||
with_test_prefix "iter $i" {
|
||||
if { [gdb_test "continue" "hit Breakpoint $::decimal, break_here .*"] != 0 } {
|
||||
# Exit if there's a failure to avoid lengthy timeouts.
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach_with_prefix displaced-stepping {off auto} {
|
||||
foreach_with_prefix target-non-stop {off on} {
|
||||
test ${displaced-stepping} ${target-non-stop}
|
||||
}
|
||||
}
|
||||
52
gdb/testsuite/gdb.threads/step-over-thread-exit.c
Normal file
52
gdb/testsuite/gdb.threads/step-over-thread-exit.c
Normal file
@@ -0,0 +1,52 @@
|
||||
/* This testcase is part of GDB, the GNU debugger.
|
||||
|
||||
Copyright 2021-2022 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 <pthread.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include "../lib/my-syscalls.h"
|
||||
|
||||
static void *
|
||||
thread_func (void *arg)
|
||||
{
|
||||
my_exit (0);
|
||||
|
||||
/* my_exit above should exit the thread, we don't expect to reach
|
||||
here. */
|
||||
abort ();
|
||||
}
|
||||
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Spawn and join a thread, 100 times. */
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
pthread_t thread;
|
||||
int ret;
|
||||
|
||||
ret = pthread_create (&thread, NULL, thread_func, NULL);
|
||||
assert (ret == 0);
|
||||
|
||||
ret = pthread_join (thread, NULL);
|
||||
assert (ret == 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
155
gdb/testsuite/gdb.threads/step-over-thread-exit.exp
Normal file
155
gdb/testsuite/gdb.threads/step-over-thread-exit.exp
Normal file
@@ -0,0 +1,155 @@
|
||||
# Copyright 2021-2022 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 stepping over a breakpoint installed on an instruction that
|
||||
# exits the thread.
|
||||
|
||||
standard_testfile .c
|
||||
|
||||
set syscalls_src $srcdir/lib/my-syscalls.S
|
||||
|
||||
if { [build_executable "failed to prepare" $testfile \
|
||||
[list $srcfile $syscalls_src] {debug pthreads}] == -1 } {
|
||||
return
|
||||
}
|
||||
|
||||
# Each argument is a different testing axis, most of them obvious.
|
||||
# NS_STOP_ALL is only used if testing "set non-stop on", and indicates
|
||||
# whether to have GDB explicitly stop all threads before continuing to
|
||||
# thread exit.
|
||||
proc test {displaced-stepping non-stop target-non-stop schedlock cmd ns_stop_all} {
|
||||
if {${non-stop} == "off" && $ns_stop_all} {
|
||||
error "invalid arguments"
|
||||
}
|
||||
|
||||
save_vars ::GDBFLAGS {
|
||||
append ::GDBFLAGS " -ex \"maintenance set target-non-stop ${target-non-stop}\""
|
||||
append ::GDBFLAGS " -ex \"set non-stop ${non-stop}\""
|
||||
clean_restart $::binfile
|
||||
}
|
||||
|
||||
gdb_test_no_output "set displaced-stepping ${displaced-stepping}"
|
||||
|
||||
if { ![runto_main] } {
|
||||
return
|
||||
}
|
||||
|
||||
gdb_breakpoint "my_exit_syscall"
|
||||
|
||||
if {$schedlock
|
||||
|| (${non-stop} == "on" && $ns_stop_all)} {
|
||||
gdb_test "continue" \
|
||||
"Thread 2 .*hit Breakpoint $::decimal.* my_exit_syscall .*" \
|
||||
"continue until syscall"
|
||||
|
||||
if {${non-stop} == "on"} {
|
||||
# The test only spawns one thread at a time, so this just
|
||||
# stops the main thread.
|
||||
gdb_test_multiple "interrupt -a" "" {
|
||||
-re "$::gdb_prompt " {
|
||||
gdb_test_multiple "" $gdb_test_name {
|
||||
-re "Thread 1 \[^\r\n\]*stopped." {
|
||||
pass $gdb_test_name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gdb_test "thread 2" "Switching to thread 2 .*"
|
||||
|
||||
gdb_test_no_output "set scheduler-locking ${schedlock}"
|
||||
|
||||
if {$cmd == "continue"} {
|
||||
gdb_test "continue" \
|
||||
"No unwaited-for children left." \
|
||||
"continue stops when thread exits"
|
||||
} else {
|
||||
gdb_test_multiple $cmd "command aborts when thread exits" {
|
||||
-re "Command aborted, thread exited\\.\r\n$::gdb_prompt " {
|
||||
pass $gdb_test_name
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gdb_test_no_output "set scheduler-locking ${schedlock}"
|
||||
|
||||
if {$cmd != "continue"} {
|
||||
set thread "<unknown>"
|
||||
gdb_test_multiple "continue" "" {
|
||||
-re -wrap "Thread ($::decimal) .*hit Breakpoint $::decimal.* my_exit_syscall .*" {
|
||||
set thread $expect_out(1,string)
|
||||
}
|
||||
}
|
||||
if {${non-stop}} {
|
||||
gdb_test -nopass "thread $thread" "Switching to thread .*" \
|
||||
"switch to event thread"
|
||||
}
|
||||
|
||||
gdb_test_multiple $cmd "command aborts when thread exits" {
|
||||
-re "Command aborted, thread exited\\.\r\n$::gdb_prompt " {
|
||||
pass $gdb_test_name
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for { set i 0 } { $i < 100 } { incr i } {
|
||||
with_test_prefix "iter $i" {
|
||||
set ok 0
|
||||
set thread "<unknown>"
|
||||
gdb_test_multiple "continue" "" {
|
||||
-re -wrap "Thread ($::decimal) .*hit Breakpoint $::decimal.* my_exit_syscall .*" {
|
||||
set thread $expect_out(1,string)
|
||||
set ok 1
|
||||
}
|
||||
}
|
||||
if {!${ok}} {
|
||||
# Exit if there's a failure to avoid lengthy
|
||||
# timeouts.
|
||||
break
|
||||
}
|
||||
|
||||
if {${non-stop}} {
|
||||
gdb_test -nopass "thread $thread" "Switching to thread .*" \
|
||||
"switch to event thread"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach_with_prefix displaced-stepping {off auto} {
|
||||
foreach_with_prefix non-stop {off on} {
|
||||
foreach_with_prefix target-non-stop {off on} {
|
||||
if {${non-stop} == "on" && ${target-non-stop} == "off"} {
|
||||
# Invalid combination.
|
||||
continue
|
||||
}
|
||||
|
||||
foreach_with_prefix schedlock {off on} {
|
||||
foreach_with_prefix cmd {"next" "continue"} {
|
||||
if {${non-stop} == "on"} {
|
||||
foreach_with_prefix ns_stop_all {0 1} {
|
||||
test ${displaced-stepping} ${non-stop} ${target-non-stop} \
|
||||
${schedlock} ${cmd} ${ns_stop_all}
|
||||
}
|
||||
} else {
|
||||
test ${displaced-stepping} ${non-stop} ${target-non-stop} ${schedlock} ${cmd} 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
90
gdb/testsuite/gdb.threads/stepi-over-clone.c
Normal file
90
gdb/testsuite/gdb.threads/stepi-over-clone.c
Normal file
@@ -0,0 +1,90 @@
|
||||
/* This testcase is part of GDB, the GNU debugger.
|
||||
|
||||
Copyright 2021-2022 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>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* Set this to non-zero from GDB to start a third worker thread. */
|
||||
volatile int start_third_thread = 0;
|
||||
|
||||
void *
|
||||
thread_worker_2 (void *arg)
|
||||
{
|
||||
int i;
|
||||
|
||||
printf ("Hello from the third thread.\n");
|
||||
fflush (stdout);
|
||||
|
||||
for (i = 0; i < 300; ++i)
|
||||
sleep (1);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *
|
||||
thread_worker_1 (void *arg)
|
||||
{
|
||||
int i;
|
||||
pthread_t thr;
|
||||
void *val;
|
||||
|
||||
if (start_third_thread)
|
||||
pthread_create (&thr, NULL, thread_worker_2, NULL);
|
||||
|
||||
printf ("Hello from the first thread.\n");
|
||||
fflush (stdout);
|
||||
|
||||
for (i = 0; i < 300; ++i)
|
||||
sleep (1);
|
||||
|
||||
if (start_third_thread)
|
||||
pthread_join (thr, &val);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *
|
||||
thread_idle_loop (void *arg)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 300; ++i)
|
||||
sleep (1);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int
|
||||
main ()
|
||||
{
|
||||
pthread_t thr, thr_idle;
|
||||
void *val;
|
||||
|
||||
if (getenv ("MAKE_EXTRA_THREAD") != NULL)
|
||||
pthread_create (&thr_idle, NULL, thread_idle_loop, NULL);
|
||||
|
||||
pthread_create (&thr, NULL, thread_worker_1, NULL);
|
||||
pthread_join (thr, &val);
|
||||
|
||||
if (getenv ("MAKE_EXTRA_THREAD") != NULL)
|
||||
pthread_join (thr_idle, &val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
392
gdb/testsuite/gdb.threads/stepi-over-clone.exp
Normal file
392
gdb/testsuite/gdb.threads/stepi-over-clone.exp
Normal file
@@ -0,0 +1,392 @@
|
||||
# Copyright 2021-2022 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 performing a 'stepi' over a clone syscall instruction.
|
||||
|
||||
# This test relies on us being able to spot syscall instructions in
|
||||
# disassembly output. For now this is only implemented for x86-64.
|
||||
if { ![istarget x86_64-*-* ] } {
|
||||
return
|
||||
}
|
||||
|
||||
standard_testfile
|
||||
|
||||
if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
|
||||
{debug pthreads additional_flags=-static}] } {
|
||||
return
|
||||
}
|
||||
|
||||
if {![runto_main]} {
|
||||
return
|
||||
}
|
||||
|
||||
# Arrange to catch the 'clone' syscall, run until we catch the
|
||||
# syscall, and try to figure out the address of the actual syscall
|
||||
# instruction so we can place a breakpoint at this address.
|
||||
|
||||
gdb_test_multiple "catch syscall group:process" "" {
|
||||
-re "The feature \'catch syscall\' is not supported.*\r\n$gdb_prompt $" {
|
||||
set supported 0
|
||||
pass $gdb_test_name
|
||||
return
|
||||
}
|
||||
-re ".*$gdb_prompt $" {
|
||||
pass $gdb_test_name
|
||||
}
|
||||
}
|
||||
|
||||
gdb_test "continue" \
|
||||
"Catchpoint $decimal \\(call to syscall clone\[23\]\\), .*"
|
||||
|
||||
# Return true if INSN is a syscall instruction.
|
||||
|
||||
proc is_syscall_insn { insn } {
|
||||
if [istarget x86_64-*-* ] {
|
||||
return { $insn == "syscall" }
|
||||
} else {
|
||||
error "port me"
|
||||
}
|
||||
}
|
||||
|
||||
# A list of addresses with syscall instructions.
|
||||
set syscall_addrs {}
|
||||
|
||||
# Get list of addresses with syscall instructions.
|
||||
gdb_test_multiple "disassemble" "" {
|
||||
-re "Dump of assembler code for function \[^\r\n\]+:\r\n" {
|
||||
exp_continue
|
||||
}
|
||||
-re "^(?:=>)?\\s+(${hex})\\s+<\\+${decimal}>:\\s+(\[^\r\n\]+)\r\n" {
|
||||
set addr $expect_out(1,string)
|
||||
set insn [string trim $expect_out(2,string)]
|
||||
if [is_syscall_insn $insn] {
|
||||
verbose -log "Found a syscall at: $addr"
|
||||
lappend syscall_addrs $addr
|
||||
}
|
||||
exp_continue
|
||||
}
|
||||
-re "^End of assembler dump\\.\r\n$gdb_prompt $" {
|
||||
if { [llength $syscall_addrs] == 0 } {
|
||||
unsupported "no syscalls found"
|
||||
return -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# The test proc. NON_STOP and DISPLACED are either 'on' or 'off', and are
|
||||
# used to configure how GDB starts up. THIRD_THREAD is either true or false,
|
||||
# and is used to configure the inferior.
|
||||
proc test {non_stop displaced third_thread} {
|
||||
global binfile srcfile
|
||||
global syscall_addrs
|
||||
global GDBFLAGS
|
||||
global gdb_prompt hex decimal
|
||||
|
||||
for { set i 0 } { $i < 3 } { incr i } {
|
||||
with_test_prefix "i=$i" {
|
||||
|
||||
# Arrange to start GDB in the correct mode.
|
||||
save_vars { GDBFLAGS } {
|
||||
append GDBFLAGS " -ex \"set non-stop $non_stop\""
|
||||
append GDBFLAGS " -ex \"set displaced $displaced\""
|
||||
clean_restart $binfile
|
||||
}
|
||||
|
||||
runto_main
|
||||
|
||||
# Setup breakpoints at all the syscall instructions we
|
||||
# might hit. Only issue one pass/fail to make tests more
|
||||
# comparable between systems.
|
||||
set test "break at syscall insns"
|
||||
foreach addr $syscall_addrs {
|
||||
if {[gdb_test -nopass "break *$addr" \
|
||||
".*" \
|
||||
$test] != 0} {
|
||||
return
|
||||
}
|
||||
}
|
||||
# If we got here, all breakpoints were set successfully.
|
||||
# We used -nopass above, so issue a pass now.
|
||||
pass $test
|
||||
|
||||
# Continue until we hit the syscall.
|
||||
gdb_test "continue"
|
||||
|
||||
if { $third_thread } {
|
||||
gdb_test_no_output "set start_third_thread=1"
|
||||
}
|
||||
|
||||
set stepi_error_count 0
|
||||
set stepi_new_thread_count 0
|
||||
set thread_1_stopped false
|
||||
set thread_2_stopped false
|
||||
set seen_prompt false
|
||||
set hello_first_thread false
|
||||
|
||||
# The program is now stopped at main, but if testing
|
||||
# against GDBserver, inferior_spawn_id is GDBserver's
|
||||
# spawn_id, and the GDBserver output emitted before the
|
||||
# program stopped isn't flushed unless we explicitly do
|
||||
# so, because it is on a different spawn_id. We could try
|
||||
# flushing it now, to avoid confusing the following tests,
|
||||
# but that would have to be done under a timeout, and
|
||||
# would thus slow down the testcase. Instead, if inferior
|
||||
# output goes to a different spawn id, then we don't need
|
||||
# to wait for the first message from the inferior with an
|
||||
# anchor, as we know consuming inferior output won't
|
||||
# consume GDB output. OTOH, if inferior output is coming
|
||||
# out on GDB's terminal, then we must use an anchor,
|
||||
# otherwise matching inferior output without one could
|
||||
# consume GDB output that we are waiting for in regular
|
||||
# expressions that are written after the inferior output
|
||||
# regular expression match.
|
||||
if {$::inferior_spawn_id != $::gdb_spawn_id} {
|
||||
set anchor ""
|
||||
} else {
|
||||
set anchor "^"
|
||||
}
|
||||
|
||||
gdb_test_multiple "stepi" "" {
|
||||
-re "^stepi\r\n" {
|
||||
verbose -log "XXX: Consume the initial command"
|
||||
exp_continue
|
||||
}
|
||||
-re "^\\\[New Thread\[^\r\n\]+\\\]\r\n" {
|
||||
verbose -log "XXX: Consume new thread line"
|
||||
incr stepi_new_thread_count
|
||||
exp_continue
|
||||
}
|
||||
-re "^\\\[Switching to Thread\[^\r\n\]+\\\]\r\n" {
|
||||
verbose -log "XXX: Consume switching to thread line"
|
||||
exp_continue
|
||||
}
|
||||
-re "^\\s*\r\n" {
|
||||
verbose -log "XXX: Consume blank line"
|
||||
exp_continue
|
||||
}
|
||||
|
||||
-i $::inferior_spawn_id
|
||||
|
||||
-re "${anchor}Hello from the first thread\\.\r\n" {
|
||||
set hello_first_thread true
|
||||
|
||||
verbose -log "XXX: Consume first worker thread message"
|
||||
if { $third_thread } {
|
||||
# If we are going to start a third thread then GDB
|
||||
# should hit the breakpoint in clone before printing
|
||||
# this message.
|
||||
incr stepi_error_count
|
||||
}
|
||||
if { !$seen_prompt } {
|
||||
exp_continue
|
||||
}
|
||||
}
|
||||
-re "^Hello from the third thread\\.\r\n" {
|
||||
# We should never see this message.
|
||||
verbose -log "XXX: Consume third worker thread message"
|
||||
incr stepi_error_count
|
||||
if { !$seen_prompt } {
|
||||
exp_continue
|
||||
}
|
||||
}
|
||||
|
||||
-i $::gdb_spawn_id
|
||||
|
||||
-re "^$hex in clone\[23\]? \\(\\)\r\n" {
|
||||
verbose -log "XXX: Consume stop location line"
|
||||
set thread_1_stopped true
|
||||
if { !$seen_prompt } {
|
||||
verbose -log "XXX: Continuing to look for the prompt"
|
||||
exp_continue
|
||||
}
|
||||
}
|
||||
-re "^$gdb_prompt " {
|
||||
verbose -log "XXX: Consume the final prompt"
|
||||
gdb_assert { $stepi_error_count == 0 }
|
||||
gdb_assert { $stepi_new_thread_count == 1 }
|
||||
set seen_prompt true
|
||||
if { $third_thread } {
|
||||
if { $non_stop } {
|
||||
# In non-stop mode if we are trying to start a
|
||||
# third thread (from the second thread), then the
|
||||
# second thread should hit the breakpoint in clone
|
||||
# before actually starting the third thread. And
|
||||
# so, at this point both thread 1, and thread 2
|
||||
# should now be stopped.
|
||||
if { !$thread_1_stopped || !$thread_2_stopped } {
|
||||
verbose -log "XXX: Continue looking for an additional stop event"
|
||||
exp_continue
|
||||
}
|
||||
} else {
|
||||
# All stop mode. Something should have stoppped
|
||||
# by now otherwise we shouldn't have a prompt, but
|
||||
# we can't know which thread will have stopped as
|
||||
# that is a race condition.
|
||||
gdb_assert { $thread_1_stopped || $thread_2_stopped }
|
||||
}
|
||||
}
|
||||
|
||||
if {$non_stop && !$hello_first_thread} {
|
||||
exp_continue
|
||||
}
|
||||
|
||||
}
|
||||
-re "^Thread 2\[^\r\n\]+ hit Breakpoint $decimal, $hex in clone\[23\]? \\(\\)\r\n" {
|
||||
verbose -log "XXX: Consume thread 2 hit breakpoint"
|
||||
set thread_2_stopped true
|
||||
if { !$seen_prompt } {
|
||||
verbose -log "XXX: Continuing to look for the prompt"
|
||||
exp_continue
|
||||
}
|
||||
}
|
||||
-re "^PC register is not available\r\n" {
|
||||
# This is the error we'd see for remote targets.
|
||||
verbose -log "XXX: Consume error line"
|
||||
incr stepi_error_count
|
||||
exp_continue
|
||||
}
|
||||
-re "^Couldn't get registers: No such process\\.\r\n" {
|
||||
# This is the error we see'd for native linux
|
||||
# targets.
|
||||
verbose -log "XXX: Consume error line"
|
||||
incr stepi_error_count
|
||||
exp_continue
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure we are back at a GDB prompt, resynchronise.
|
||||
verbose -log "XXX: Have completed scanning the 'stepi' output"
|
||||
gdb_test "p 1 + 2 + 3" " = 6"
|
||||
|
||||
# Check the number of threads we have, it should be exactly two.
|
||||
set thread_count 0
|
||||
set bad_threads 0
|
||||
|
||||
# Build up our expectations for what the current thread state
|
||||
# should be. Thread 1 is the easiest, this is the thread we are
|
||||
# stepping, so this thread should always be stopped, and should
|
||||
# always still be in clone.
|
||||
set match_code {}
|
||||
lappend match_code {
|
||||
-re "\\*?\\s+1\\s+Thread\[^\r\n\]+clone\[23\]? \\(\\)\r\n" {
|
||||
incr thread_count
|
||||
exp_continue
|
||||
}
|
||||
}
|
||||
|
||||
# What state should thread 2 be in?
|
||||
if { $non_stop == "on" } {
|
||||
if { $third_thread } {
|
||||
# With non-stop mode on, and creation of a third thread
|
||||
# having been requested, we expect Thread 2 to exist, and
|
||||
# be stopped at the breakpoint in clone (just before the
|
||||
# third thread is actually created).
|
||||
lappend match_code {
|
||||
-re "\\*?\\s+2\\s+Thread\[^\r\n\]+$hex in clone\[23\]? \\(\\)\r\n" {
|
||||
incr thread_count
|
||||
exp_continue
|
||||
}
|
||||
-re "\\*?\\s+2\\s+Thread\[^\r\n\]+\\(running\\)\r\n" {
|
||||
incr thread_count
|
||||
incr bad_threads
|
||||
exp_continue
|
||||
}
|
||||
-re "\\*?\\s+2\\s+Thread\[^\r\n\]+\r\n" {
|
||||
verbose -log "XXX: thread 2 is bad, unknown state"
|
||||
incr thread_count
|
||||
incr bad_threads
|
||||
exp_continue
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
# With non-stop mode on, and no third thread having been
|
||||
# requested, then we expect Thread 2 to exist, and still
|
||||
# be running.
|
||||
lappend match_code {
|
||||
-re "\\*?\\s+2\\s+Thread\[^\r\n\]+\\(running\\)\r\n" {
|
||||
incr thread_count
|
||||
exp_continue
|
||||
}
|
||||
-re "\\*?\\s+2\\s+Thread\[^\r\n\]+\r\n" {
|
||||
verbose -log "XXX: thread 2 is bad, unknown state"
|
||||
incr thread_count
|
||||
incr bad_threads
|
||||
exp_continue
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
# With non-stop mode off then we expect Thread 2 to exist, and
|
||||
# be stopped. We don't have any guarantee about where the
|
||||
# thread will have stopped though, so we need to be vague.
|
||||
lappend match_code {
|
||||
-re "\\*?\\s+2\\s+Thread\[^\r\n\]+\\(running\\)\r\n" {
|
||||
verbose -log "XXX: thread 2 is bad, unexpectedly running"
|
||||
incr thread_count
|
||||
incr bad_threads
|
||||
exp_continue
|
||||
}
|
||||
-re "\\*?\\s+2\\s+Thread\[^\r\n\]+_start\[^\r\n\]+\r\n" {
|
||||
# We know that the thread shouldn't be stopped
|
||||
# at _start, though. This is the location of
|
||||
# the scratch pad on Linux at the time of
|
||||
# writting.
|
||||
verbose -log "XXX: thread 2 is bad, stuck in scratchpad"
|
||||
incr thread_count
|
||||
incr bad_threads
|
||||
exp_continue
|
||||
}
|
||||
-re "\\*?\\s+2\\s+Thread\[^\r\n\]+\r\n" {
|
||||
incr thread_count
|
||||
exp_continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# We don't expect to ever see a thread 3. Even when we are
|
||||
# requesting that this third thread be created, thread 2, the
|
||||
# thread that creates thread 3, should stop before executing the
|
||||
# clone syscall. So, if we do ever see this then something has
|
||||
# gone wrong.
|
||||
lappend match_code {
|
||||
-re "\\s+3\\s+Thread\[^\r\n\]+\r\n" {
|
||||
incr thread_count
|
||||
incr bad_threads
|
||||
exp_continue
|
||||
}
|
||||
}
|
||||
|
||||
lappend match_code {
|
||||
-re "$gdb_prompt $" {
|
||||
gdb_assert { $thread_count == 2 }
|
||||
gdb_assert { $bad_threads == 0 }
|
||||
}
|
||||
}
|
||||
|
||||
set match_code [join $match_code]
|
||||
gdb_test_multiple "info threads" "" $match_code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Run the test in all suitable configurations.
|
||||
foreach_with_prefix third_thread { false true } {
|
||||
foreach_with_prefix non-stop { "on" "off" } {
|
||||
foreach_with_prefix displaced { "off" "on" } {
|
||||
test ${non-stop} ${displaced} ${third_thread}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,17 +155,7 @@ if {$is_remote} {
|
||||
exp_continue
|
||||
}
|
||||
|
||||
# When PR gdb/30129 is fixed then this can all be collapsed down
|
||||
# into a single gdb_assert call. This is split out like this
|
||||
# because the SAW_BP_DELETED part is working, and we want to
|
||||
# spot if that stops working.
|
||||
if { $saw_thread_exited && $saw_bp_deleted } {
|
||||
kpass "gdb/30129" $gdb_test_name
|
||||
} elseif {!$saw_thread_exited && $saw_bp_deleted} {
|
||||
kfail "gdb/30129" $gdb_test_name
|
||||
} else {
|
||||
fail $gdb_test_name
|
||||
}
|
||||
gdb_assert { $saw_thread_exited && $saw_bp_deleted } $gdb_test_name
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -21,38 +21,56 @@
|
||||
|
||||
#include <asm/unistd.h>
|
||||
|
||||
/* int my_execve (const char *file, char *argv[], char *envp[]); */
|
||||
|
||||
.global my_execve
|
||||
my_execve:
|
||||
/* The SYSCALL macro below current supports calling syscalls with up
|
||||
to 3 arguments, and, assumes the syscall never returns, like exec
|
||||
and exit. If you need to call syscalls with more arguments or you
|
||||
need to call syscalls that actually return, you'll need to update
|
||||
the macros. We don't bother with optimizing setting up fewer
|
||||
arguments for syscalls that take fewer arguments, as we're not
|
||||
optimizating for speed or space, but for maintainability. */
|
||||
|
||||
#if defined(__x86_64__)
|
||||
|
||||
mov $__NR_execve, %rax
|
||||
/* rdi, rsi and rdx already contain the right arguments. */
|
||||
my_execve_syscall:
|
||||
syscall
|
||||
ret
|
||||
#define SYSCALL(NAME, NR) \
|
||||
.global NAME ;\
|
||||
NAME: ;\
|
||||
mov $NR, %rax ;\
|
||||
/* rdi, rsi and rdx already contain the right arguments. */ \
|
||||
NAME ## _syscall: ;\
|
||||
syscall ;\
|
||||
ret ;
|
||||
|
||||
#elif defined(__i386__)
|
||||
|
||||
mov $__NR_execve, %eax
|
||||
mov 4(%esp), %ebx
|
||||
mov 8(%esp), %ecx
|
||||
mov 12(%esp), %edx
|
||||
my_execve_syscall:
|
||||
int $0x80
|
||||
#define SYSCALL(NAME, NR) \
|
||||
.global NAME ;\
|
||||
NAME: ;\
|
||||
mov $NR, %eax ;\
|
||||
mov 4(%esp), %ebx ;\
|
||||
mov 8(%esp), %ecx ;\
|
||||
mov 12(%esp), %edx ;\
|
||||
NAME ## _syscall: ;\
|
||||
int $0x80 ;\
|
||||
ret
|
||||
|
||||
#elif defined(__aarch64__)
|
||||
|
||||
mov x8, #__NR_execve
|
||||
/* x0, x1 and x2 already contain the right arguments. */
|
||||
my_execve_syscall:
|
||||
#define SYSCALL(NAME, NR) \
|
||||
.global NAME ;\
|
||||
NAME: ;\
|
||||
mov x8, NR ;\
|
||||
/* x0, x1 and x2 already contain the right arguments. */ \
|
||||
NAME ## _syscall: ;\
|
||||
svc #0
|
||||
|
||||
#else
|
||||
# error "Unsupported architecture"
|
||||
#endif
|
||||
|
||||
SYSCALL (my_execve, __NR_execve)
|
||||
|
||||
/* void my_exit (int code); */
|
||||
|
||||
SYSCALL (my_exit, __NR_exit)
|
||||
|
||||
.section .note.GNU-stack,"",@progbits
|
||||
|
||||
@@ -22,4 +22,9 @@
|
||||
|
||||
int my_execve (const char *file, char *argv[], char *envp[]);
|
||||
|
||||
/* `exit` syscall, which makes the thread exit (as opposed to
|
||||
`exit_group`, which makes the process exit). */
|
||||
|
||||
void my_exit (int code);
|
||||
|
||||
#endif /* MY_SYSCALLS_H */
|
||||
|
||||
71
gdb/thread.c
71
gdb/thread.c
@@ -193,7 +193,8 @@ clear_thread_inferior_resources (struct thread_info *tp)
|
||||
/* See gdbthread.h. */
|
||||
|
||||
void
|
||||
set_thread_exited (thread_info *tp, bool silent)
|
||||
set_thread_exited (thread_info *tp, gdb::optional<ULONGEST> exit_code,
|
||||
bool silent)
|
||||
{
|
||||
/* Dead threads don't need to step-over. Remove from chain. */
|
||||
if (thread_is_in_step_over_chain (tp))
|
||||
@@ -212,7 +213,22 @@ set_thread_exited (thread_info *tp, bool silent)
|
||||
if (proc_target != nullptr)
|
||||
proc_target->maybe_remove_resumed_with_pending_wait_status (tp);
|
||||
|
||||
gdb::observers::thread_exit.notify (tp, silent);
|
||||
if (!silent && print_thread_events)
|
||||
{
|
||||
if (exit_code.has_value ())
|
||||
{
|
||||
gdb_printf (_("[%s exited with code %s]\n"),
|
||||
target_pid_to_str (tp->ptid).c_str (),
|
||||
pulongest (*exit_code));
|
||||
}
|
||||
else
|
||||
{
|
||||
gdb_printf (_("[%s exited]\n"),
|
||||
target_pid_to_str (tp->ptid).c_str ());
|
||||
}
|
||||
}
|
||||
|
||||
gdb::observers::thread_exit.notify (tp, exit_code, silent);
|
||||
|
||||
/* Tag it as exited. */
|
||||
tp->state = THREAD_EXITED;
|
||||
@@ -236,7 +252,7 @@ init_thread_list (void)
|
||||
highest_thread_num = 0;
|
||||
|
||||
for (inferior *inf : all_inferiors ())
|
||||
inf->clear_thread_list (true);
|
||||
inf->clear_thread_list ();
|
||||
}
|
||||
|
||||
/* Allocate a new thread of inferior INF with target id PTID and add
|
||||
@@ -399,6 +415,24 @@ thread_info::clear_pending_waitstatus ()
|
||||
|
||||
/* See gdbthread.h. */
|
||||
|
||||
void
|
||||
thread_info::set_thread_options (gdb_thread_options thread_options)
|
||||
{
|
||||
gdb_assert (this->state != THREAD_EXITED);
|
||||
gdb_assert (!this->executing ());
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -451,20 +485,22 @@ global_thread_step_over_chain_remove (struct thread_info *tp)
|
||||
global_thread_step_over_list.erase (it);
|
||||
}
|
||||
|
||||
/* Delete the thread referenced by THR. If SILENT, don't notify
|
||||
the observer of this exit.
|
||||
|
||||
THR must not be NULL or a failed assertion will be raised. */
|
||||
/* Helper for the different delete_thread variants. */
|
||||
|
||||
static void
|
||||
delete_thread_1 (thread_info *thr, bool silent)
|
||||
delete_thread_1 (thread_info *thr, gdb::optional<ULONGEST> exit_code,
|
||||
bool silent)
|
||||
{
|
||||
gdb_assert (thr != nullptr);
|
||||
|
||||
threads_debug_printf ("deleting thread %s, silent = %d",
|
||||
thr->ptid.to_string ().c_str (), silent);
|
||||
threads_debug_printf ("deleting thread %s, exit_code = %s, silent = %d",
|
||||
thr->ptid.to_string ().c_str (),
|
||||
(exit_code.has_value ()
|
||||
? pulongest (*exit_code)
|
||||
: "<none>"),
|
||||
silent);
|
||||
|
||||
set_thread_exited (thr, silent);
|
||||
set_thread_exited (thr, exit_code, silent);
|
||||
|
||||
if (!thr->deletable ())
|
||||
{
|
||||
@@ -480,16 +516,25 @@ delete_thread_1 (thread_info *thr, bool silent)
|
||||
|
||||
/* See gdbthread.h. */
|
||||
|
||||
void
|
||||
delete_thread_with_exit_code (thread_info *thread, ULONGEST exit_code,
|
||||
bool silent)
|
||||
{
|
||||
delete_thread_1 (thread, exit_code, false /* not silent */);
|
||||
}
|
||||
|
||||
/* See gdbthread.h. */
|
||||
|
||||
void
|
||||
delete_thread (thread_info *thread)
|
||||
{
|
||||
delete_thread_1 (thread, false /* not silent */);
|
||||
delete_thread_1 (thread, {}, false /* not silent */);
|
||||
}
|
||||
|
||||
void
|
||||
delete_thread_silent (thread_info *thread)
|
||||
{
|
||||
delete_thread_1 (thread, true /* silent */);
|
||||
delete_thread_1 (thread, {}, true /* not silent */);
|
||||
}
|
||||
|
||||
struct thread_info *
|
||||
|
||||
@@ -612,21 +612,13 @@ windows_nat_target::delete_thread (ptid_t ptid, DWORD exit_code,
|
||||
|
||||
id = ptid.lwp ();
|
||||
|
||||
/* Emit a notification about the thread being deleted.
|
||||
|
||||
Note that no notification was printed when the main thread
|
||||
/* Note that no notification was printed when the main thread
|
||||
was created, and thus, unless in verbose mode, we should be
|
||||
symmetrical, and avoid that notification for the main thread
|
||||
here as well. */
|
||||
|
||||
if (info_verbose)
|
||||
gdb_printf ("[Deleting %s]\n", target_pid_to_str (ptid).c_str ());
|
||||
else if (print_thread_events && !main_thread_p)
|
||||
gdb_printf (_("[%s exited with code %u]\n"),
|
||||
target_pid_to_str (ptid).c_str (),
|
||||
(unsigned) exit_code);
|
||||
|
||||
::delete_thread (find_thread_ptid (this, ptid));
|
||||
bool silent = (main_thread_p && !info_verbose);
|
||||
thread_info *todel = find_thread_ptid (this, ptid);
|
||||
delete_thread_with_exit_code (todel, exit_code, silent);
|
||||
|
||||
auto iter = std::find_if (windows_process.thread_list.begin (),
|
||||
windows_process.thread_list.end (),
|
||||
|
||||
@@ -80,6 +80,9 @@ struct thread_info
|
||||
|
||||
/* 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;
|
||||
|
||||
@@ -144,6 +144,18 @@ is_leader (thread_info *thread)
|
||||
return ptid.pid () == ptid.lwp ();
|
||||
}
|
||||
|
||||
/* Return true if we should report thread exit events to GDB, for
|
||||
THR. */
|
||||
|
||||
static bool
|
||||
report_exit_events_for (thread_info *thr)
|
||||
{
|
||||
client_state &cs = get_client_state ();
|
||||
|
||||
return (cs.report_thread_events
|
||||
|| (thr->thread_options & GDB_THREAD_OPTION_EXIT) != 0);
|
||||
}
|
||||
|
||||
/* LWP accessors. */
|
||||
|
||||
/* See nat/linux-nat.h. */
|
||||
@@ -267,7 +279,8 @@ int using_threads = 1;
|
||||
static int stabilizing_threads;
|
||||
|
||||
static void unsuspend_all_lwps (struct lwp_info *except);
|
||||
static void mark_lwp_dead (struct lwp_info *lwp, int wstat);
|
||||
static void mark_lwp_dead (struct lwp_info *lwp, int wstat,
|
||||
bool thread_event);
|
||||
static int lwp_is_marked_dead (struct lwp_info *lwp);
|
||||
static int kill_lwp (unsigned long lwpid, int signo);
|
||||
static void enqueue_pending_signal (struct lwp_info *lwp, int signal, siginfo_t *info);
|
||||
@@ -491,7 +504,6 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
|
||||
struct lwp_info *event_lwp = *orig_event_lwp;
|
||||
int event = linux_ptrace_get_extended_event (wstat);
|
||||
struct thread_info *event_thr = get_lwp_thread (event_lwp);
|
||||
struct lwp_info *new_lwp;
|
||||
|
||||
gdb_assert (event_lwp->waitstatus.kind () == TARGET_WAITKIND_IGNORE);
|
||||
|
||||
@@ -503,7 +515,6 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
|
||||
if ((event == PTRACE_EVENT_FORK) || (event == PTRACE_EVENT_VFORK)
|
||||
|| (event == PTRACE_EVENT_CLONE))
|
||||
{
|
||||
ptid_t ptid;
|
||||
unsigned long new_pid;
|
||||
int ret, status;
|
||||
|
||||
@@ -527,61 +538,65 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
|
||||
warning ("wait returned unexpected status 0x%x", status);
|
||||
}
|
||||
|
||||
if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
|
||||
if (debug_threads)
|
||||
{
|
||||
struct process_info *parent_proc;
|
||||
struct process_info *child_proc;
|
||||
struct lwp_info *child_lwp;
|
||||
struct thread_info *child_thr;
|
||||
debug_printf ("HEW: Got %s event from LWP %ld, new child is %ld\n",
|
||||
(event == PTRACE_EVENT_FORK ? "fork"
|
||||
: event == PTRACE_EVENT_VFORK ? "vfork"
|
||||
: event == PTRACE_EVENT_CLONE ? "clone"
|
||||
: "???"),
|
||||
ptid_of (event_thr).lwp (),
|
||||
new_pid);
|
||||
}
|
||||
|
||||
ptid = ptid_t (new_pid, new_pid);
|
||||
ptid_t child_ptid = (event != PTRACE_EVENT_CLONE
|
||||
? ptid_t (new_pid, new_pid)
|
||||
: ptid_t (ptid_of (event_thr).pid (), new_pid));
|
||||
|
||||
threads_debug_printf ("Got fork event from LWP %ld, "
|
||||
"new child is %d",
|
||||
ptid_of (event_thr).lwp (),
|
||||
ptid.pid ());
|
||||
lwp_info *child_lwp = add_lwp (child_ptid);
|
||||
gdb_assert (child_lwp != NULL);
|
||||
child_lwp->stopped = 1;
|
||||
if (event != PTRACE_EVENT_CLONE)
|
||||
child_lwp->must_set_ptrace_flags = 1;
|
||||
child_lwp->status_pending_p = 0;
|
||||
|
||||
thread_info *child_thr = get_lwp_thread (child_lwp);
|
||||
|
||||
/* If we're suspending all threads, leave this one suspended
|
||||
too. If the fork/clone parent is stepping over a breakpoint,
|
||||
all other threads have been suspended already. Leave the
|
||||
child suspended too. */
|
||||
if (stopping_threads == STOPPING_AND_SUSPENDING_THREADS
|
||||
|| event_lwp->bp_reinsert != 0)
|
||||
{
|
||||
threads_debug_printf ("leaving child suspended");
|
||||
child_lwp->suspended = 1;
|
||||
}
|
||||
|
||||
if (event_lwp->bp_reinsert != 0
|
||||
&& supports_software_single_step ()
|
||||
&& event == PTRACE_EVENT_VFORK)
|
||||
{
|
||||
/* If we leave single-step breakpoints there, child will
|
||||
hit it, so uninsert single-step breakpoints from parent
|
||||
(and child). Once vfork child is done, reinsert
|
||||
them back to parent. */
|
||||
uninsert_single_step_breakpoints (event_thr);
|
||||
}
|
||||
|
||||
if (event != PTRACE_EVENT_CLONE)
|
||||
{
|
||||
/* Add the new process to the tables and clone the breakpoint
|
||||
lists of the parent. We need to do this even if the new process
|
||||
will be detached, since we will need the process object and the
|
||||
breakpoints to remove any breakpoints from memory when we
|
||||
detach, and the client side will access registers. */
|
||||
child_proc = add_linux_process (new_pid, 0);
|
||||
process_info *child_proc = add_linux_process (new_pid, 0);
|
||||
gdb_assert (child_proc != NULL);
|
||||
child_lwp = add_lwp (ptid);
|
||||
gdb_assert (child_lwp != NULL);
|
||||
child_lwp->stopped = 1;
|
||||
child_lwp->must_set_ptrace_flags = 1;
|
||||
child_lwp->status_pending_p = 0;
|
||||
child_thr = get_lwp_thread (child_lwp);
|
||||
child_thr->last_resume_kind = resume_stop;
|
||||
child_thr->last_status.set_stopped (GDB_SIGNAL_0);
|
||||
|
||||
/* If we're suspending all threads, leave this one suspended
|
||||
too. If the fork/clone parent is stepping over a breakpoint,
|
||||
all other threads have been suspended already. Leave the
|
||||
child suspended too. */
|
||||
if (stopping_threads == STOPPING_AND_SUSPENDING_THREADS
|
||||
|| event_lwp->bp_reinsert != 0)
|
||||
{
|
||||
threads_debug_printf ("leaving child suspended");
|
||||
child_lwp->suspended = 1;
|
||||
}
|
||||
|
||||
parent_proc = get_thread_process (event_thr);
|
||||
process_info *parent_proc = get_thread_process (event_thr);
|
||||
child_proc->attached = parent_proc->attached;
|
||||
|
||||
if (event_lwp->bp_reinsert != 0
|
||||
&& supports_software_single_step ()
|
||||
&& event == PTRACE_EVENT_VFORK)
|
||||
{
|
||||
/* If we leave single-step breakpoints there, child will
|
||||
hit it, so uninsert single-step breakpoints from parent
|
||||
(and child). Once vfork child is done, reinsert
|
||||
them back to parent. */
|
||||
uninsert_single_step_breakpoints (event_thr);
|
||||
}
|
||||
|
||||
clone_all_breakpoints (child_thr, event_thr);
|
||||
|
||||
target_desc_up tdesc = allocate_target_description ();
|
||||
@@ -590,88 +605,97 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
|
||||
|
||||
/* Clone arch-specific process data. */
|
||||
low_new_fork (parent_proc, child_proc);
|
||||
}
|
||||
|
||||
/* Save fork info in the parent thread. */
|
||||
if (event == PTRACE_EVENT_FORK)
|
||||
event_lwp->waitstatus.set_forked (ptid);
|
||||
else if (event == PTRACE_EVENT_VFORK)
|
||||
event_lwp->waitstatus.set_vforked (ptid);
|
||||
/* Save fork/clone info in the parent thread. */
|
||||
if (event == PTRACE_EVENT_FORK)
|
||||
event_lwp->waitstatus.set_forked (child_ptid);
|
||||
else if (event == PTRACE_EVENT_VFORK)
|
||||
event_lwp->waitstatus.set_vforked (child_ptid);
|
||||
else if (event == PTRACE_EVENT_CLONE
|
||||
&& (event_thr->thread_options & GDB_THREAD_OPTION_CLONE) != 0)
|
||||
event_lwp->waitstatus.set_thread_cloned (child_ptid);
|
||||
|
||||
if (event != PTRACE_EVENT_CLONE
|
||||
|| (event_thr->thread_options & GDB_THREAD_OPTION_CLONE) != 0)
|
||||
{
|
||||
/* The status_pending field contains bits denoting the
|
||||
extended event, so when the pending event is handled,
|
||||
the handler will look at lwp->waitstatus. */
|
||||
extended event, so when the pending event is handled, the
|
||||
handler will look at lwp->waitstatus. */
|
||||
event_lwp->status_pending_p = 1;
|
||||
event_lwp->status_pending = wstat;
|
||||
|
||||
/* Link the threads until the parent event is passed on to
|
||||
higher layers. */
|
||||
/* Link the threads until the parent's event is passed on to
|
||||
GDB. */
|
||||
event_lwp->fork_relative = child_lwp;
|
||||
child_lwp->fork_relative = event_lwp;
|
||||
|
||||
/* If the parent thread is doing step-over with single-step
|
||||
breakpoints, the list of single-step breakpoints are cloned
|
||||
from the parent's. Remove them from the child process.
|
||||
In case of vfork, we'll reinsert them back once vforked
|
||||
child is done. */
|
||||
if (event_lwp->bp_reinsert != 0
|
||||
&& supports_software_single_step ())
|
||||
{
|
||||
/* The child process is forked and stopped, so it is safe
|
||||
to access its memory without stopping all other threads
|
||||
from other processes. */
|
||||
delete_single_step_breakpoints (child_thr);
|
||||
|
||||
gdb_assert (has_single_step_breakpoints (event_thr));
|
||||
gdb_assert (!has_single_step_breakpoints (child_thr));
|
||||
}
|
||||
|
||||
/* Report the event. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
threads_debug_printf
|
||||
("Got clone event from LWP %ld, new child is LWP %ld",
|
||||
lwpid_of (event_thr), new_pid);
|
||||
/* If the parent thread is doing step-over with single-step
|
||||
breakpoints, the list of single-step breakpoints are cloned
|
||||
from the parent's. Remove them from the child process.
|
||||
In case of vfork, we'll reinsert them back once vforked
|
||||
child is done. */
|
||||
if (event_lwp->bp_reinsert != 0
|
||||
&& supports_software_single_step ())
|
||||
{
|
||||
/* The child process is forked and stopped, so it is safe
|
||||
to access its memory without stopping all other threads
|
||||
from other processes. */
|
||||
delete_single_step_breakpoints (child_thr);
|
||||
|
||||
ptid = ptid_t (pid_of (event_thr), new_pid);
|
||||
new_lwp = add_lwp (ptid);
|
||||
|
||||
/* Either we're going to immediately resume the new thread
|
||||
or leave it stopped. resume_one_lwp is a nop if it
|
||||
thinks the thread is currently running, so set this first
|
||||
before calling resume_one_lwp. */
|
||||
new_lwp->stopped = 1;
|
||||
|
||||
/* If we're suspending all threads, leave this one suspended
|
||||
too. If the fork/clone parent is stepping over a breakpoint,
|
||||
all other threads have been suspended already. Leave the
|
||||
child suspended too. */
|
||||
if (stopping_threads == STOPPING_AND_SUSPENDING_THREADS
|
||||
|| event_lwp->bp_reinsert != 0)
|
||||
new_lwp->suspended = 1;
|
||||
gdb_assert (has_single_step_breakpoints (event_thr));
|
||||
gdb_assert (!has_single_step_breakpoints (child_thr));
|
||||
}
|
||||
|
||||
/* Normally we will get the pending SIGSTOP. But in some cases
|
||||
we might get another signal delivered to the group first.
|
||||
If we do get another signal, be sure not to lose it. */
|
||||
if (WSTOPSIG (status) != SIGSTOP)
|
||||
{
|
||||
new_lwp->stop_expected = 1;
|
||||
new_lwp->status_pending_p = 1;
|
||||
new_lwp->status_pending = status;
|
||||
child_lwp->stop_expected = 1;
|
||||
child_lwp->status_pending_p = 1;
|
||||
child_lwp->status_pending = status;
|
||||
}
|
||||
else if (cs.report_thread_events)
|
||||
else if (event == PTRACE_EVENT_CLONE && cs.report_thread_events)
|
||||
{
|
||||
new_lwp->waitstatus.set_thread_created ();
|
||||
new_lwp->status_pending_p = 1;
|
||||
new_lwp->status_pending = status;
|
||||
child_lwp->waitstatus.set_thread_created ();
|
||||
child_lwp->status_pending_p = 1;
|
||||
child_lwp->status_pending = status;
|
||||
}
|
||||
|
||||
if (event == PTRACE_EVENT_CLONE)
|
||||
{
|
||||
#ifdef USE_THREAD_DB
|
||||
thread_db_notice_clone (event_thr, ptid);
|
||||
thread_db_notice_clone (event_thr, child_ptid);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Don't report the event. */
|
||||
return 1;
|
||||
if (event == PTRACE_EVENT_CLONE
|
||||
&& (event_thr->thread_options & GDB_THREAD_OPTION_CLONE) == 0)
|
||||
{
|
||||
threads_debug_printf
|
||||
("not reporting clone event from LWP %ld, new child is %ld\n",
|
||||
ptid_of (event_thr).lwp (),
|
||||
new_pid);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Leave the child stopped until GDB processes the parent
|
||||
event. */
|
||||
child_thr->last_resume_kind = resume_stop;
|
||||
child_thr->last_status.set_stopped (GDB_SIGNAL_0);
|
||||
|
||||
/* Report the event. */
|
||||
threads_debug_printf
|
||||
("reporting %s event from LWP %ld, new child is %ld\n",
|
||||
(event == PTRACE_EVENT_FORK ? "fork"
|
||||
: event == PTRACE_EVENT_VFORK ? "vfork"
|
||||
: event == PTRACE_EVENT_CLONE ? "clone"
|
||||
: "???"),
|
||||
ptid_of (event_thr).lwp (),
|
||||
new_pid);
|
||||
return 0;
|
||||
}
|
||||
else if (event == PTRACE_EVENT_VFORK_DONE)
|
||||
{
|
||||
@@ -1777,10 +1801,12 @@ iterate_over_lwps (ptid_t filter,
|
||||
return get_thread_lwp (thread);
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
linux_process_target::check_zombie_leaders ()
|
||||
{
|
||||
for_each_process ([this] (process_info *proc)
|
||||
bool new_pending_event = false;
|
||||
|
||||
for_each_process ([&] (process_info *proc)
|
||||
{
|
||||
pid_t leader_pid = pid_of (proc);
|
||||
lwp_info *leader_lp = find_lwp_pid (ptid_t (leader_pid));
|
||||
@@ -1849,9 +1875,19 @@ linux_process_target::check_zombie_leaders ()
|
||||
"(it exited, or another thread execd), "
|
||||
"deleting it.",
|
||||
leader_pid);
|
||||
delete_lwp (leader_lp);
|
||||
|
||||
thread_info *leader_thread = get_lwp_thread (leader_lp);
|
||||
if (report_exit_events_for (leader_thread))
|
||||
{
|
||||
mark_lwp_dead (leader_lp, W_EXITCODE (0, 0), true);
|
||||
new_pending_event = true;
|
||||
}
|
||||
else
|
||||
delete_lwp (leader_lp);
|
||||
}
|
||||
});
|
||||
|
||||
return new_pending_event;
|
||||
}
|
||||
|
||||
/* Callback for `find_thread'. Returns the first LWP that is not
|
||||
@@ -2219,7 +2255,6 @@ linux_low_ptrace_options (int attached)
|
||||
void
|
||||
linux_process_target::filter_event (int lwpid, int wstat)
|
||||
{
|
||||
client_state &cs = get_client_state ();
|
||||
struct lwp_info *child;
|
||||
struct thread_info *thread;
|
||||
int have_stop_pc = 0;
|
||||
@@ -2306,12 +2341,12 @@ linux_process_target::filter_event (int lwpid, int wstat)
|
||||
/* If this is not the leader LWP, then the exit signal was not
|
||||
the end of the debugged application and should be ignored,
|
||||
unless GDB wants to hear about thread exits. */
|
||||
if (cs.report_thread_events || is_leader (thread))
|
||||
if (report_exit_events_for (thread) || is_leader (thread))
|
||||
{
|
||||
/* Since events are serialized to GDB core, and we can't
|
||||
report this one right now. Leave the status pending for
|
||||
the next time we're able to report it. */
|
||||
mark_lwp_dead (child, wstat);
|
||||
mark_lwp_dead (child, wstat, false);
|
||||
return;
|
||||
}
|
||||
else
|
||||
@@ -2624,7 +2659,8 @@ linux_process_target::wait_for_event_filtered (ptid_t wait_ptid,
|
||||
|
||||
/* Check for zombie thread group leaders. Those can't be reaped
|
||||
until all other threads in the thread group are. */
|
||||
check_zombie_leaders ();
|
||||
if (check_zombie_leaders ())
|
||||
goto retry;
|
||||
|
||||
auto not_stopped = [&] (thread_info *thread)
|
||||
{
|
||||
@@ -2868,13 +2904,31 @@ ptid_t
|
||||
linux_process_target::filter_exit_event (lwp_info *event_child,
|
||||
target_waitstatus *ourstatus)
|
||||
{
|
||||
client_state &cs = get_client_state ();
|
||||
struct thread_info *thread = get_lwp_thread (event_child);
|
||||
ptid_t ptid = ptid_of (thread);
|
||||
|
||||
if (ourstatus->kind () == TARGET_WAITKIND_THREAD_EXITED)
|
||||
{
|
||||
/* We're reporting a thread exit for the leader. The exit was
|
||||
detected by check_zombie_leaders. */
|
||||
gdb_assert (is_leader (thread));
|
||||
gdb_assert (report_exit_events_for (thread));
|
||||
|
||||
delete_lwp (event_child);
|
||||
return ptid;
|
||||
}
|
||||
|
||||
/* Note we must filter TARGET_WAITKIND_SIGNALLED as well, otherwise
|
||||
if a non-leader thread exits with a signal, we'd report it to the
|
||||
core which would interpret it as the whole-process exiting.
|
||||
There is no TARGET_WAITKIND_THREAD_SIGNALLED event kind. */
|
||||
if (ourstatus->kind () != TARGET_WAITKIND_EXITED
|
||||
&& ourstatus->kind () != TARGET_WAITKIND_SIGNALLED)
|
||||
return ptid;
|
||||
|
||||
if (!is_leader (thread))
|
||||
{
|
||||
if (cs.report_thread_events)
|
||||
if (report_exit_events_for (thread))
|
||||
ourstatus->set_thread_exited (0);
|
||||
else
|
||||
ourstatus->set_ignore ();
|
||||
@@ -2934,7 +2988,6 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
||||
int report_to_gdb;
|
||||
int trace_event;
|
||||
int in_step_range;
|
||||
int any_resumed;
|
||||
|
||||
threads_debug_printf ("[%s]", target_pid_to_str (ptid).c_str ());
|
||||
|
||||
@@ -2948,23 +3001,7 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
||||
in_step_range = 0;
|
||||
ourstatus->set_ignore ();
|
||||
|
||||
auto status_pending_p_any = [&] (thread_info *thread)
|
||||
{
|
||||
return status_pending_p_callback (thread, minus_one_ptid);
|
||||
};
|
||||
|
||||
auto not_stopped = [&] (thread_info *thread)
|
||||
{
|
||||
return not_stopped_callback (thread, minus_one_ptid);
|
||||
};
|
||||
|
||||
/* Find a resumed LWP, if any. */
|
||||
if (find_thread (status_pending_p_any) != NULL)
|
||||
any_resumed = 1;
|
||||
else if (find_thread (not_stopped) != NULL)
|
||||
any_resumed = 1;
|
||||
else
|
||||
any_resumed = 0;
|
||||
bool was_any_resumed = any_resumed ();
|
||||
|
||||
if (step_over_bkpt == null_ptid)
|
||||
pid = wait_for_event (ptid, &w, options);
|
||||
@@ -2975,7 +3012,7 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
||||
pid = wait_for_event (step_over_bkpt, &w, options & ~WNOHANG);
|
||||
}
|
||||
|
||||
if (pid == 0 || (pid == -1 && !any_resumed))
|
||||
if (pid == 0 || (pid == -1 && !was_any_resumed))
|
||||
{
|
||||
gdb_assert (target_options & TARGET_WNOHANG);
|
||||
|
||||
@@ -3000,7 +3037,20 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
||||
{
|
||||
if (WIFEXITED (w))
|
||||
{
|
||||
ourstatus->set_exited (WEXITSTATUS (w));
|
||||
/* If we already have the exit recorded in waitstatus, use
|
||||
it. This will happen when we detect a zombie leader,
|
||||
when we had GDB_THREAD_OPTION_EXIT enabled for it. We
|
||||
want to report its exit as TARGET_WAITKIND_THREAD_EXITED,
|
||||
as the whole process hasn't exited yet. */
|
||||
const target_waitstatus &ws = event_child->waitstatus;
|
||||
if (ws.kind () != TARGET_WAITKIND_IGNORE)
|
||||
{
|
||||
gdb_assert (ws.kind () == TARGET_WAITKIND_EXITED
|
||||
|| ws.kind () == TARGET_WAITKIND_THREAD_EXITED);
|
||||
*ourstatus = ws;
|
||||
}
|
||||
else
|
||||
ourstatus->set_exited (WEXITSTATUS (w));
|
||||
|
||||
threads_debug_printf
|
||||
("ret = %s, exited with retcode %d",
|
||||
@@ -3017,10 +3067,7 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
||||
WTERMSIG (w));
|
||||
}
|
||||
|
||||
if (ourstatus->kind () == TARGET_WAITKIND_EXITED)
|
||||
return filter_exit_event (event_child, ourstatus);
|
||||
|
||||
return ptid_of (current_thread);
|
||||
return filter_exit_event (event_child, ourstatus);
|
||||
}
|
||||
|
||||
/* If step-over executes a breakpoint instruction, in the case of a
|
||||
@@ -3527,7 +3574,8 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
||||
|
||||
/* Break the unreported fork relationship chain. */
|
||||
if (event_child->waitstatus.kind () == TARGET_WAITKIND_FORKED
|
||||
|| event_child->waitstatus.kind () == TARGET_WAITKIND_VFORKED)
|
||||
|| event_child->waitstatus.kind () == TARGET_WAITKIND_VFORKED
|
||||
|| event_child->waitstatus.kind () == TARGET_WAITKIND_THREAD_CLONED)
|
||||
{
|
||||
event_child->fork_relative->fork_relative = NULL;
|
||||
event_child->fork_relative = NULL;
|
||||
@@ -3588,10 +3636,7 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
||||
target_pid_to_str (ptid_of (current_thread)).c_str (),
|
||||
ourstatus->to_string ().c_str ());
|
||||
|
||||
if (ourstatus->kind () == TARGET_WAITKIND_EXITED)
|
||||
return filter_exit_event (event_child, ourstatus);
|
||||
|
||||
return ptid_of (current_thread);
|
||||
return filter_exit_event (event_child, ourstatus);
|
||||
}
|
||||
|
||||
/* Get rid of any pending event in the pipe. */
|
||||
@@ -3624,7 +3669,6 @@ linux_process_target::wait (ptid_t ptid,
|
||||
event_ptid = wait_1 (ptid, ourstatus, target_options);
|
||||
}
|
||||
while ((target_options & TARGET_WNOHANG) == 0
|
||||
&& event_ptid == null_ptid
|
||||
&& ourstatus->kind () == TARGET_WAITKIND_IGNORE);
|
||||
|
||||
/* If at least one stop was reported, there may be more. A single
|
||||
@@ -3714,8 +3758,15 @@ suspend_and_send_sigstop (thread_info *thread, lwp_info *except)
|
||||
send_sigstop (thread, except);
|
||||
}
|
||||
|
||||
/* Mark LWP dead, with WSTAT as exit status pending to report later.
|
||||
If THREAD_EVENT is true, interpret WSTAT as a thread exit event
|
||||
instead of a process exit event. This is meaningful for the leader
|
||||
thread, as we normally report a process-wide exit event when we see
|
||||
the leader exit, and a thread exit event when we see any other
|
||||
thread exit. */
|
||||
|
||||
static void
|
||||
mark_lwp_dead (struct lwp_info *lwp, int wstat)
|
||||
mark_lwp_dead (struct lwp_info *lwp, int wstat, bool thread_event)
|
||||
{
|
||||
/* Store the exit status for later. */
|
||||
lwp->status_pending_p = 1;
|
||||
@@ -3724,9 +3775,19 @@ mark_lwp_dead (struct lwp_info *lwp, int wstat)
|
||||
/* Store in waitstatus as well, as there's nothing else to process
|
||||
for this event. */
|
||||
if (WIFEXITED (wstat))
|
||||
lwp->waitstatus.set_exited (WEXITSTATUS (wstat));
|
||||
{
|
||||
if (thread_event)
|
||||
lwp->waitstatus.set_thread_exited (WEXITSTATUS (wstat));
|
||||
else
|
||||
lwp->waitstatus.set_exited (WEXITSTATUS (wstat));
|
||||
}
|
||||
else if (WIFSIGNALED (wstat))
|
||||
lwp->waitstatus.set_signalled (gdb_signal_from_host (WTERMSIG (wstat)));
|
||||
{
|
||||
gdb_assert (!thread_event);
|
||||
lwp->waitstatus.set_signalled (gdb_signal_from_host (WTERMSIG (wstat)));
|
||||
}
|
||||
else
|
||||
gdb_assert_not_reached ("unknown status kind");
|
||||
|
||||
/* Prevent trying to stop it. */
|
||||
lwp->stopped = 1;
|
||||
@@ -4263,15 +4324,14 @@ linux_set_resume_request (thread_info *thread, thread_resume *resume, size_t n)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Don't let wildcard resumes resume fork children that GDB
|
||||
does not yet know are new fork children. */
|
||||
/* Don't let wildcard resumes resume fork/vfork/clone
|
||||
children that GDB does not yet know are new children. */
|
||||
if (lwp->fork_relative != NULL)
|
||||
{
|
||||
struct lwp_info *rel = lwp->fork_relative;
|
||||
|
||||
if (rel->status_pending_p
|
||||
&& (rel->waitstatus.kind () == TARGET_WAITKIND_FORKED
|
||||
|| rel->waitstatus.kind () == TARGET_WAITKIND_VFORKED))
|
||||
&& is_new_child_status (rel->waitstatus.kind ()))
|
||||
{
|
||||
threads_debug_printf
|
||||
("not resuming LWP %ld: has queued stop reply",
|
||||
@@ -5893,6 +5953,14 @@ linux_process_target::supports_vfork_events ()
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Return the set of supported thread options. */
|
||||
|
||||
gdb_thread_options
|
||||
linux_process_target::supported_thread_options ()
|
||||
{
|
||||
return GDB_THREAD_OPTION_CLONE | GDB_THREAD_OPTION_EXIT;
|
||||
}
|
||||
|
||||
/* Check if exec events are supported. */
|
||||
|
||||
bool
|
||||
@@ -6135,6 +6203,32 @@ linux_process_target::thread_stopped (thread_info *thread)
|
||||
return get_thread_lwp (thread)->stopped;
|
||||
}
|
||||
|
||||
bool
|
||||
linux_process_target::any_resumed ()
|
||||
{
|
||||
bool any_resumed;
|
||||
|
||||
auto status_pending_p_any = [&] (thread_info *thread)
|
||||
{
|
||||
return status_pending_p_callback (thread, minus_one_ptid);
|
||||
};
|
||||
|
||||
auto not_stopped = [&] (thread_info *thread)
|
||||
{
|
||||
return not_stopped_callback (thread, minus_one_ptid);
|
||||
};
|
||||
|
||||
/* Find a resumed LWP, if any. */
|
||||
if (find_thread (status_pending_p_any) != NULL)
|
||||
any_resumed = 1;
|
||||
else if (find_thread (not_stopped) != NULL)
|
||||
any_resumed = 1;
|
||||
else
|
||||
any_resumed = 0;
|
||||
|
||||
return any_resumed;
|
||||
}
|
||||
|
||||
/* This exposes stop-all-threads functionality to other modules. */
|
||||
|
||||
void
|
||||
@@ -6920,9 +7014,10 @@ linux_process_target::thread_pending_parent (thread_info *thread)
|
||||
}
|
||||
|
||||
thread_info *
|
||||
linux_process_target::thread_pending_child (thread_info *thread)
|
||||
linux_process_target::thread_pending_child (thread_info *thread,
|
||||
target_waitkind *kind)
|
||||
{
|
||||
lwp_info *child = get_thread_lwp (thread)->pending_child ();
|
||||
lwp_info *child = get_thread_lwp (thread)->pending_child (kind);
|
||||
|
||||
if (child == nullptr)
|
||||
return nullptr;
|
||||
|
||||
@@ -234,6 +234,8 @@ public:
|
||||
|
||||
bool supports_vfork_events () override;
|
||||
|
||||
gdb_thread_options supported_thread_options () override;
|
||||
|
||||
bool supports_exec_events () override;
|
||||
|
||||
void handle_new_gdb_connection () override;
|
||||
@@ -257,6 +259,8 @@ public:
|
||||
|
||||
bool thread_stopped (thread_info *thread) override;
|
||||
|
||||
bool any_resumed () override;
|
||||
|
||||
void pause_all (bool freeze) override;
|
||||
|
||||
void unpause_all (bool unfreeze) override;
|
||||
@@ -313,7 +317,8 @@ public:
|
||||
#endif
|
||||
|
||||
thread_info *thread_pending_parent (thread_info *thread) override;
|
||||
thread_info *thread_pending_child (thread_info *thread) override;
|
||||
thread_info *thread_pending_child (thread_info *thread,
|
||||
target_waitkind *kind) override;
|
||||
|
||||
bool supports_catch_syscall () override;
|
||||
|
||||
@@ -569,13 +574,15 @@ private: /* Back to private. */
|
||||
|
||||
/* Detect zombie thread group leaders, and "exit" them. We can't
|
||||
reap their exits until all other threads in the group have
|
||||
exited. */
|
||||
void check_zombie_leaders ();
|
||||
exited. Returns true if we left any new event pending, false
|
||||
otherwise. */
|
||||
bool check_zombie_leaders ();
|
||||
|
||||
/* Convenience function that is called when the kernel reports an exit
|
||||
event. This decides whether to report the event to GDB as a
|
||||
process exit event, a thread exit event, or to suppress the
|
||||
event. */
|
||||
/* Convenience function that is called when we're about to return an
|
||||
event to the core. If the event is an exit or signalled event,
|
||||
then this decides whether to report it as process-wide event, as
|
||||
a thread exit event, or to suppress it. All other event kinds
|
||||
are passed through unmodified. */
|
||||
ptid_t filter_exit_event (lwp_info *event_child,
|
||||
target_waitstatus *ourstatus);
|
||||
|
||||
@@ -732,8 +739,8 @@ struct pending_signal
|
||||
|
||||
struct lwp_info
|
||||
{
|
||||
/* If this LWP is a fork child that wasn't reported to GDB yet, return
|
||||
its parent, else nullptr. */
|
||||
/* If this LWP is a fork/vfork/clone child that wasn't reported to
|
||||
GDB yet, return its parent, else nullptr. */
|
||||
lwp_info *pending_parent () const
|
||||
{
|
||||
if (this->fork_relative == nullptr)
|
||||
@@ -741,10 +748,10 @@ struct lwp_info
|
||||
|
||||
gdb_assert (this->fork_relative->fork_relative == this);
|
||||
|
||||
/* In a fork parent/child relationship, the parent has a status pending and
|
||||
the child does not, and a thread can only be in one such relationship
|
||||
at most. So we can recognize who is the parent based on which one has
|
||||
a pending status. */
|
||||
/* In a parent/child relationship, the parent has a status pending
|
||||
and the child does not, and a thread can only be in one such
|
||||
relationship at most. So we can recognize who is the parent
|
||||
based on which one has a pending status. */
|
||||
gdb_assert (!!this->status_pending_p
|
||||
!= !!this->fork_relative->status_pending_p);
|
||||
|
||||
@@ -754,24 +761,25 @@ struct lwp_info
|
||||
const target_waitstatus &ws
|
||||
= this->fork_relative->waitstatus;
|
||||
gdb_assert (ws.kind () == TARGET_WAITKIND_FORKED
|
||||
|| ws.kind () == TARGET_WAITKIND_VFORKED);
|
||||
|| ws.kind () == TARGET_WAITKIND_VFORKED
|
||||
|| ws.kind () == TARGET_WAITKIND_THREAD_CLONED);
|
||||
|
||||
return this->fork_relative;
|
||||
}
|
||||
|
||||
/* If this LWP is the parent of a fork child we haven't reported to GDB yet,
|
||||
return that child, else nullptr. */
|
||||
lwp_info *pending_child () const
|
||||
/* If this LWP is the parent of a fork/vfork/clone child we haven't
|
||||
reported to GDB yet, return that child, else nullptr. */
|
||||
lwp_info *pending_child (target_waitkind *kind) const
|
||||
{
|
||||
if (this->fork_relative == nullptr)
|
||||
return nullptr;
|
||||
|
||||
gdb_assert (this->fork_relative->fork_relative == this);
|
||||
|
||||
/* In a fork parent/child relationship, the parent has a status pending and
|
||||
the child does not, and a thread can only be in one such relationship
|
||||
at most. So we can recognize who is the parent based on which one has
|
||||
a pending status. */
|
||||
/* In a parent/child relationship, the parent has a status pending
|
||||
and the child does not, and a thread can only be in one such
|
||||
relationship at most. So we can recognize who is the parent
|
||||
based on which one has a pending status. */
|
||||
gdb_assert (!!this->status_pending_p
|
||||
!= !!this->fork_relative->status_pending_p);
|
||||
|
||||
@@ -780,8 +788,10 @@ struct lwp_info
|
||||
|
||||
const target_waitstatus &ws = this->waitstatus;
|
||||
gdb_assert (ws.kind () == TARGET_WAITKIND_FORKED
|
||||
|| ws.kind () == TARGET_WAITKIND_VFORKED);
|
||||
|| ws.kind () == TARGET_WAITKIND_VFORKED
|
||||
|| ws.kind () == TARGET_WAITKIND_THREAD_CLONED);
|
||||
|
||||
*kind = ws.kind ();
|
||||
return this->fork_relative;
|
||||
}
|
||||
|
||||
|
||||
@@ -1062,6 +1062,7 @@ prepare_resume_reply (char *buf, ptid_t ptid, const target_waitstatus &status)
|
||||
case TARGET_WAITKIND_FORKED:
|
||||
case TARGET_WAITKIND_VFORKED:
|
||||
case TARGET_WAITKIND_VFORK_DONE:
|
||||
case TARGET_WAITKIND_THREAD_CLONED:
|
||||
case TARGET_WAITKIND_EXECD:
|
||||
case TARGET_WAITKIND_THREAD_CREATED:
|
||||
case TARGET_WAITKIND_SYSCALL_ENTRY:
|
||||
@@ -1071,13 +1072,30 @@ prepare_resume_reply (char *buf, ptid_t ptid, const target_waitstatus &status)
|
||||
struct regcache *regcache;
|
||||
char *buf_start = buf;
|
||||
|
||||
if ((status.kind () == TARGET_WAITKIND_FORKED && cs.report_fork_events)
|
||||
if ((status.kind () == TARGET_WAITKIND_FORKED
|
||||
&& cs.report_fork_events)
|
||||
|| (status.kind () == TARGET_WAITKIND_VFORKED
|
||||
&& cs.report_vfork_events))
|
||||
&& cs.report_vfork_events)
|
||||
|| status.kind () == TARGET_WAITKIND_THREAD_CLONED)
|
||||
{
|
||||
enum gdb_signal signal = GDB_SIGNAL_TRAP;
|
||||
const char *event = (status.kind () == TARGET_WAITKIND_FORKED
|
||||
? "fork" : "vfork");
|
||||
|
||||
auto kind_remote_str = [] (target_waitkind kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case TARGET_WAITKIND_FORKED:
|
||||
return "fork";
|
||||
case TARGET_WAITKIND_VFORKED:
|
||||
return "vfork";
|
||||
case TARGET_WAITKIND_THREAD_CLONED:
|
||||
return "clone";
|
||||
default:
|
||||
gdb_assert_not_reached ("unhandled kind");
|
||||
}
|
||||
};
|
||||
|
||||
const char *event = kind_remote_str (status.kind ());
|
||||
|
||||
sprintf (buf, "T%02x%s:", signal, event);
|
||||
buf += strlen (buf);
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#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"
|
||||
@@ -241,7 +242,8 @@ in_queued_stop_replies_ptid (struct notif_event *event, ptid_t filter_ptid)
|
||||
|
||||
/* Don't resume fork children that GDB does not know about yet. */
|
||||
if ((vstop_event->status.kind () == TARGET_WAITKIND_FORKED
|
||||
|| vstop_event->status.kind () == TARGET_WAITKIND_VFORKED)
|
||||
|| vstop_event->status.kind () == TARGET_WAITKIND_VFORKED
|
||||
|| vstop_event->status.kind () == TARGET_WAITKIND_THREAD_CLONED)
|
||||
&& vstop_event->status.child_ptid ().matches (filter_ptid))
|
||||
return true;
|
||||
|
||||
@@ -615,6 +617,17 @@ parse_store_memtags_request (char *request, CORE_ADDR *addr, size_t *len,
|
||||
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
|
||||
@@ -896,6 +909,114 @@ handle_general_set (char *own_buf)
|
||||
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:");
|
||||
@@ -1228,8 +1349,9 @@ handle_detach (char *own_buf)
|
||||
continue;
|
||||
|
||||
/* Only threads that have a pending fork event. */
|
||||
thread_info *child = target_thread_pending_child (thread);
|
||||
if (child == nullptr)
|
||||
target_waitkind kind;
|
||||
thread_info *child = target_thread_pending_child (thread, &kind);
|
||||
if (child == nullptr || kind == TARGET_WAITKIND_THREAD_CLONED)
|
||||
continue;
|
||||
|
||||
process_info *fork_child_process = get_thread_process (child);
|
||||
@@ -1650,9 +1772,10 @@ handle_qxfer_threads_worker (thread_info *thread, std::string *buffer)
|
||||
gdb_byte *handle;
|
||||
bool handle_status = target_thread_handle (ptid, &handle, &handle_len);
|
||||
|
||||
/* If this is a fork or vfork child (has a fork parent), GDB does not yet
|
||||
know about this process, and must not know about it until it gets the
|
||||
corresponding (v)fork event. Exclude this thread from the list. */
|
||||
/* If this is a (v)fork/clone child (has a (v)fork/clone parent),
|
||||
GDB does not yet know about this thread, and must not know about
|
||||
it until it gets the corresponding (v)fork/clone event. Exclude
|
||||
this thread from the list. */
|
||||
if (target_thread_pending_parent (thread) != nullptr)
|
||||
return;
|
||||
|
||||
@@ -2347,6 +2470,8 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
|
||||
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
|
||||
@@ -2473,6 +2598,14 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
|
||||
|
||||
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+");
|
||||
@@ -2912,6 +3045,7 @@ resume (struct thread_resume *actions, size_t num_actions)
|
||||
|
||||
if (cs.last_status.kind () != TARGET_WAITKIND_EXITED
|
||||
&& cs.last_status.kind () != TARGET_WAITKIND_SIGNALLED
|
||||
&& cs.last_status.kind () != TARGET_WAITKIND_THREAD_EXITED
|
||||
&& cs.last_status.kind () != TARGET_WAITKIND_NO_RESUMED)
|
||||
current_thread->last_status = cs.last_status;
|
||||
|
||||
@@ -4590,7 +4724,17 @@ handle_target_event (int err, gdb_client_data client_data)
|
||||
}
|
||||
}
|
||||
else
|
||||
push_stop_notification (cs.last_ptid, cs.last_status);
|
||||
{
|
||||
push_stop_notification (cs.last_ptid, cs.last_status);
|
||||
|
||||
if (cs.last_status.kind () == TARGET_WAITKIND_THREAD_EXITED
|
||||
&& !target_any_resumed ())
|
||||
{
|
||||
target_waitstatus ws;
|
||||
ws.set_no_resumed ();
|
||||
push_stop_notification (null_ptid, ws);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Be sure to not change the selected thread behind GDB's back.
|
||||
|
||||
@@ -532,6 +532,12 @@ process_stratum_target::supports_vfork_events ()
|
||||
return false;
|
||||
}
|
||||
|
||||
gdb_thread_options
|
||||
process_stratum_target::supported_thread_options ()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
process_stratum_target::supports_exec_events ()
|
||||
{
|
||||
@@ -608,6 +614,12 @@ process_stratum_target::thread_stopped (thread_info *thread)
|
||||
gdb_assert_not_reached ("target op thread_stopped not supported");
|
||||
}
|
||||
|
||||
bool
|
||||
process_stratum_target::any_resumed ()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
process_stratum_target::supports_get_tib_address ()
|
||||
{
|
||||
@@ -810,7 +822,8 @@ process_stratum_target::thread_pending_parent (thread_info *thread)
|
||||
}
|
||||
|
||||
thread_info *
|
||||
process_stratum_target::thread_pending_child (thread_info *thread)
|
||||
process_stratum_target::thread_pending_child (thread_info *thread,
|
||||
target_waitkind *kind)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -276,6 +276,9 @@ public:
|
||||
/* 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 ();
|
||||
|
||||
@@ -316,6 +319,9 @@ public:
|
||||
/* Return true if THREAD is known to be stopped now. */
|
||||
virtual bool thread_stopped (thread_info *thread);
|
||||
|
||||
/* Return true if any thread is known to be resumed. */
|
||||
virtual bool any_resumed ();
|
||||
|
||||
/* Return true if the get_tib_address op is supported. */
|
||||
virtual bool supports_get_tib_address ();
|
||||
|
||||
@@ -475,13 +481,14 @@ public:
|
||||
virtual bool thread_handle (ptid_t ptid, gdb_byte **handle,
|
||||
int *handle_len);
|
||||
|
||||
/* If THREAD is a fork child that was not reported to GDB, return its parent
|
||||
else nullptr. */
|
||||
/* If THREAD is a fork/vfork/clone child that was not reported to
|
||||
GDB, return its parent else nullptr. */
|
||||
virtual thread_info *thread_pending_parent (thread_info *thread);
|
||||
|
||||
/* If THREAD is the parent of a fork child that was not reported to GDB,
|
||||
return this child, else nullptr. */
|
||||
virtual thread_info *thread_pending_child (thread_info *thread);
|
||||
/* If THREAD is the parent of a fork/vfork/clone child that was not
|
||||
reported to GDB, return this child, else nullptr. */
|
||||
virtual thread_info *thread_pending_child (thread_info *thread,
|
||||
target_waitkind *kind);
|
||||
|
||||
/* Returns true if the target can software single step. */
|
||||
virtual bool supports_software_single_step ();
|
||||
@@ -531,6 +538,9 @@ int kill_inferior (process_info *proc);
|
||||
#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 ()
|
||||
|
||||
@@ -675,6 +685,9 @@ target_read_btrace_conf (struct btrace_target_info *tinfo,
|
||||
#define target_supports_software_single_step() \
|
||||
the_target->supports_software_single_step ()
|
||||
|
||||
#define target_any_resumed() \
|
||||
the_target->any_resumed ()
|
||||
|
||||
ptid_t mywait (ptid_t ptid, struct target_waitstatus *ourstatus,
|
||||
target_wait_flags options, int connected_wait);
|
||||
|
||||
@@ -694,9 +707,9 @@ target_thread_pending_parent (thread_info *thread)
|
||||
}
|
||||
|
||||
static inline thread_info *
|
||||
target_thread_pending_child (thread_info *thread)
|
||||
target_thread_pending_child (thread_info *thread, target_waitkind *kind)
|
||||
{
|
||||
return the_target->thread_pending_child (thread);
|
||||
return the_target->thread_pending_child (thread, kind);
|
||||
}
|
||||
|
||||
/* Read LEN bytes from MEMADDR in the buffer MYADDR. Return 0 if the read
|
||||
|
||||
Reference in New Issue
Block a user