forked from Imagelibrary/binutils-gdb
Compare commits
30 Commits
gdb-14-bra
...
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
|
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.
|
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
|
* New convenience function "$_shell", to execute a shell command and
|
||||||
return the result. This lets you run shell commands in expressions.
|
return the result. This lets you run shell commands in expressions.
|
||||||
Some examples:
|
Some examples:
|
||||||
@@ -104,6 +108,22 @@ show always-read-ctf
|
|||||||
without a thread restriction. The same is also true for the 'task'
|
without a thread restriction. The same is also true for the 'task'
|
||||||
field of an Ada task-specific breakpoint.
|
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
|
*** Changes in GDB 13
|
||||||
|
|
||||||
* MI version 1 is deprecated, and will be removed in GDB 14.
|
* 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
|
from the current process state. GDB will show this additional information
|
||||||
automatically, or through one of the memory-tag subcommands.
|
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
|
* "info breakpoints" now displays enabled breakpoint locations of
|
||||||
disabled breakpoints as in the "y-" state. For example:
|
disabled breakpoints as in the "y-" state. For example:
|
||||||
|
|
||||||
|
|||||||
@@ -233,7 +233,9 @@ annotate_thread_changed (void)
|
|||||||
/* Emit notification on thread exit. */
|
/* Emit notification on thread exit. */
|
||||||
|
|
||||||
static void
|
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)
|
if (annotation_level > 1)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3243,7 +3243,9 @@ remove_breakpoints (void)
|
|||||||
that thread. */
|
that thread. */
|
||||||
|
|
||||||
static void
|
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 ())
|
for (breakpoint *b : all_breakpoints_safe ())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -192,12 +192,18 @@ write_memory_ptid (ptid_t ptid, CORE_ADDR memaddr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
displaced_step_instruction_executed_successfully (gdbarch *arch,
|
displaced_step_instruction_executed_successfully
|
||||||
gdb_signal signal)
|
(gdbarch *arch, const target_waitstatus &status)
|
||||||
{
|
{
|
||||||
if (signal != GDB_SIGNAL_TRAP)
|
if (status.kind () == TARGET_WAITKIND_STOPPED
|
||||||
|
&& status.sig () != GDB_SIGNAL_TRAP)
|
||||||
return false;
|
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 (target_stopped_by_watchpoint ())
|
||||||
{
|
{
|
||||||
if (gdbarch_have_nonsteppable_watchpoint (arch)
|
if (gdbarch_have_nonsteppable_watchpoint (arch)
|
||||||
@@ -210,7 +216,7 @@ displaced_step_instruction_executed_successfully (gdbarch *arch,
|
|||||||
|
|
||||||
displaced_step_finish_status
|
displaced_step_finish_status
|
||||||
displaced_step_buffers::finish (gdbarch *arch, thread_info *thread,
|
displaced_step_buffers::finish (gdbarch *arch, thread_info *thread,
|
||||||
gdb_signal sig)
|
const target_waitstatus &status)
|
||||||
{
|
{
|
||||||
gdb_assert (thread->displaced_step_state.in_progress ());
|
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 (),
|
thread->ptid.to_string ().c_str (),
|
||||||
paddress (arch, buffer->addr));
|
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);
|
regcache *rc = get_thread_regcache (thread);
|
||||||
|
|
||||||
bool instruction_executed_successfully
|
bool instruction_executed_successfully
|
||||||
= displaced_step_instruction_executed_successfully (arch, sig);
|
= displaced_step_instruction_executed_successfully (arch, status);
|
||||||
|
|
||||||
if (instruction_executed_successfully)
|
if (instruction_executed_successfully)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ struct displaced_step_buffers
|
|||||||
CORE_ADDR &displaced_pc);
|
CORE_ADDR &displaced_pc);
|
||||||
|
|
||||||
displaced_step_finish_status finish (gdbarch *arch, thread_info *thread,
|
displaced_step_finish_status finish (gdbarch *arch, thread_info *thread,
|
||||||
gdb_signal sig);
|
const target_waitstatus &status);
|
||||||
|
|
||||||
const displaced_step_copy_insn_closure *
|
const displaced_step_copy_insn_closure *
|
||||||
copy_insn_closure_by_addr (CORE_ADDR addr);
|
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.
|
There is no locking and any thread may run at any time.
|
||||||
|
|
||||||
@item on
|
@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
|
@item step
|
||||||
Behaves like @code{on} when stepping, and @code{off} otherwise.
|
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 @code{QThreadEvents}
|
||||||
@tab Tracking thread lifetime.
|
@tab Tracking thread lifetime.
|
||||||
|
|
||||||
|
@item @code{thread-options}
|
||||||
|
@tab @code{QThreadOptions}
|
||||||
|
@tab Set thread event reporting options.
|
||||||
|
|
||||||
@item @code{no-resumed-stop-reply}
|
@item @code{no-resumed-stop-reply}
|
||||||
@tab @code{no resumed thread left stop reply}
|
@tab @code{no resumed thread left stop reply}
|
||||||
@tab Tracking thread lifetime.
|
@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
|
remote stub must also supply the appropriate @samp{qSupported} feature
|
||||||
indicating support.
|
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
|
@cindex thread create event, remote reply
|
||||||
@anchor{thread create event}
|
@anchor{thread create event}
|
||||||
@item create
|
@item create
|
||||||
@@ -42667,9 +42684,10 @@ hex strings.
|
|||||||
@item w @var{AA} ; @var{tid}
|
@item w @var{AA} ; @var{tid}
|
||||||
|
|
||||||
The thread exited, and @var{AA} is the exit status. This response
|
The thread exited, and @var{AA} is the exit status. This response
|
||||||
should not be sent by default; @value{GDBN} requests it with the
|
should not be sent by default; @value{GDBN} requests it with either
|
||||||
@ref{QThreadEvents} packet. See also @ref{thread create event} above.
|
the @ref{QThreadEvents} or @ref{QThreadOptions} packets. See also
|
||||||
@var{AA} is formatted as a big-endian hex string.
|
@ref{thread create event} above. @var{AA} is formatted as a
|
||||||
|
big-endian hex string.
|
||||||
|
|
||||||
@item N
|
@item N
|
||||||
There are no resumed threads left in the target. In other words, even
|
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
|
stub reports that it supports it by including @samp{QThreadEvents+} in
|
||||||
its @samp{qSupported} reply.
|
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:
|
Reply:
|
||||||
@table @samp
|
@table @samp
|
||||||
@item OK
|
@item OK
|
||||||
@@ -43410,6 +43433,94 @@ the stub.
|
|||||||
Use of this packet is controlled by the @code{set remote thread-events}
|
Use of this packet is controlled by the @code{set remote thread-events}
|
||||||
command (@pxref{Remote Configuration, 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}
|
@item qRcmd,@var{command}
|
||||||
@cindex execute remote command, remote request
|
@cindex execute remote command, remote request
|
||||||
@cindex @samp{qRcmd} packet
|
@cindex @samp{qRcmd} packet
|
||||||
@@ -43855,6 +43966,11 @@ These are the currently defined stub features and their properties:
|
|||||||
@tab @samp{-}
|
@tab @samp{-}
|
||||||
@tab No
|
@tab No
|
||||||
|
|
||||||
|
@item @samp{QThreadOptions}
|
||||||
|
@tab Yes
|
||||||
|
@tab @samp{-}
|
||||||
|
@tab No
|
||||||
|
|
||||||
@item @samp{no-resumed}
|
@item @samp{no-resumed}
|
||||||
@tab No
|
@tab No
|
||||||
@tab @samp{-}
|
@tab @samp{-}
|
||||||
@@ -44076,6 +44192,13 @@ The remote stub reports the supported actions in the reply to
|
|||||||
@item QThreadEvents
|
@item QThreadEvents
|
||||||
The remote stub understands the @samp{QThreadEvents} packet.
|
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
|
@item no-resumed
|
||||||
The remote stub reports the @samp{N} stop reply.
|
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",
|
fbsd_lwp_debug_printf ("deleting thread for LWP %u",
|
||||||
pl.pl_lwpid);
|
pl.pl_lwpid);
|
||||||
if (print_thread_events)
|
|
||||||
gdb_printf (_("[%s exited]\n"),
|
|
||||||
target_pid_to_str (wptid).c_str ());
|
|
||||||
low_delete_thread (thr);
|
low_delete_thread (thr);
|
||||||
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 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);
|
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);
|
It is possible for the displaced-stepped instruction to have caused
|
||||||
extern displaced_step_finish_status gdbarch_displaced_step_finish (struct gdbarch *gdbarch, thread_info *thread, gdb_signal sig);
|
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);
|
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. */
|
/* 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
|
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 != NULL);
|
||||||
gdb_assert (gdbarch->displaced_step_finish != NULL);
|
gdb_assert (gdbarch->displaced_step_finish != NULL);
|
||||||
if (gdbarch_debug >= 2)
|
if (gdbarch_debug >= 2)
|
||||||
gdb_printf (gdb_stdlog, "gdbarch_displaced_step_finish called\n");
|
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
|
void
|
||||||
|
|||||||
@@ -1875,10 +1875,14 @@ Throw an exception if any unexpected error happens.
|
|||||||
Method(
|
Method(
|
||||||
comment="""
|
comment="""
|
||||||
Clean up after a displaced step of THREAD.
|
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",
|
type="displaced_step_finish_status",
|
||||||
name="displaced_step_finish",
|
name="displaced_step_finish",
|
||||||
params=[("thread_info *", "thread"), ("gdb_signal", "sig")],
|
params=[("thread_info *", "thread"), ("const target_waitstatus &", "ws")],
|
||||||
predefault="NULL",
|
predefault="NULL",
|
||||||
invalid="(! gdbarch->displaced_step_finish) != (! gdbarch->displaced_step_prepare)",
|
invalid="(! gdbarch->displaced_step_finish) != (! gdbarch->displaced_step_prepare)",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ struct symtab;
|
|||||||
#include "ui-out.h"
|
#include "ui-out.h"
|
||||||
#include "btrace.h"
|
#include "btrace.h"
|
||||||
#include "target/waitstatus.h"
|
#include "target/waitstatus.h"
|
||||||
|
#include "target/target.h"
|
||||||
#include "cli/cli-utils.h"
|
#include "cli/cli-utils.h"
|
||||||
#include "gdbsupport/refcounted-object.h"
|
#include "gdbsupport/refcounted-object.h"
|
||||||
#include "gdbsupport/common-gdbthread.h"
|
#include "gdbsupport/common-gdbthread.h"
|
||||||
@@ -470,6 +471,17 @@ public:
|
|||||||
m_thread_fsm = std::move (fsm);
|
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;
|
int current_line = 0;
|
||||||
struct symtab *current_symtab = NULL;
|
struct symtab *current_symtab = NULL;
|
||||||
|
|
||||||
@@ -577,6 +589,10 @@ private:
|
|||||||
left to do for the thread's execution command after the target
|
left to do for the thread's execution command after the target
|
||||||
stops. Several execution commands use it. */
|
stops. Several execution commands use it. */
|
||||||
std::unique_ptr<struct thread_fsm> m_thread_fsm;
|
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
|
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
|
/* Delete thread THREAD and notify of thread exit. If the thread is
|
||||||
currently not deletable, don't actually delete it but still tag it
|
currently not deletable, don't actually delete it but still tag it
|
||||||
as exited and do the notification. */
|
as exited and do the notification. EXIT_CODE is the thread's exit
|
||||||
extern void delete_thread (struct thread_info *thread);
|
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
|
/* Like delete_thread, but be quiet about it. Used when the process
|
||||||
this thread belonged to has already exited, for example. */
|
this thread belonged to has already exited, for example. */
|
||||||
extern void delete_thread_silent (struct thread_info *thread);
|
extern void delete_thread_silent (struct thread_info *thread);
|
||||||
|
|
||||||
/* Mark the thread exited, but don't delete it or remove it from the
|
/* Mark the thread exited, but don't delete it or remove it from the
|
||||||
inferior thread list. */
|
inferior thread list. EXIT_CODE is the thread's exit code, if
|
||||||
extern void set_thread_exited (thread_info *tp, bool silent);
|
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. */
|
/* Delete a step_resume_breakpoint from the thread database. */
|
||||||
extern void delete_step_resume_breakpoint (struct thread_info *);
|
extern void delete_step_resume_breakpoint (struct thread_info *);
|
||||||
|
|||||||
@@ -217,13 +217,13 @@ add_inferior (int pid)
|
|||||||
/* See inferior.h. */
|
/* See inferior.h. */
|
||||||
|
|
||||||
void
|
void
|
||||||
inferior::clear_thread_list (bool silent)
|
inferior::clear_thread_list ()
|
||||||
{
|
{
|
||||||
thread_list.clear_and_dispose ([=] (thread_info *thr)
|
thread_list.clear_and_dispose ([=] (thread_info *thr)
|
||||||
{
|
{
|
||||||
threads_debug_printf ("deleting thread %s, silent = %d",
|
threads_debug_printf ("deleting thread %s",
|
||||||
thr->ptid.to_string ().c_str (), silent);
|
thr->ptid.to_string ().c_str ());
|
||||||
set_thread_exited (thr, silent);
|
set_thread_exited (thr, {}, true);
|
||||||
if (thr->deletable ())
|
if (thr->deletable ())
|
||||||
delete thr;
|
delete thr;
|
||||||
});
|
});
|
||||||
@@ -233,7 +233,7 @@ inferior::clear_thread_list (bool silent)
|
|||||||
void
|
void
|
||||||
delete_inferior (struct inferior *inf)
|
delete_inferior (struct inferior *inf)
|
||||||
{
|
{
|
||||||
inf->clear_thread_list (true);
|
inf->clear_thread_list ();
|
||||||
|
|
||||||
auto it = inferior_list.iterator_to (*inf);
|
auto it = inferior_list.iterator_to (*inf);
|
||||||
inferior_list.erase (it);
|
inferior_list.erase (it);
|
||||||
@@ -259,7 +259,7 @@ delete_inferior (struct inferior *inf)
|
|||||||
static void
|
static void
|
||||||
exit_inferior_1 (struct inferior *inf, int silent)
|
exit_inferior_1 (struct inferior *inf, int silent)
|
||||||
{
|
{
|
||||||
inf->clear_thread_list (silent);
|
inf->clear_thread_list ();
|
||||||
|
|
||||||
gdb::observers::inferior_exit.notify (inf);
|
gdb::observers::inferior_exit.notify (inf);
|
||||||
|
|
||||||
|
|||||||
@@ -490,9 +490,8 @@ public:
|
|||||||
inline safe_inf_threads_range threads_safe ()
|
inline safe_inf_threads_range threads_safe ()
|
||||||
{ return safe_inf_threads_range (this->thread_list.begin ()); }
|
{ return safe_inf_threads_range (this->thread_list.begin ()); }
|
||||||
|
|
||||||
/* Delete all threads in the thread list. If SILENT, exit threads
|
/* Delete all threads in the thread list, silently. */
|
||||||
silently. */
|
void clear_thread_list ();
|
||||||
void clear_thread_list (bool silent);
|
|
||||||
|
|
||||||
/* Continuations-related methods. A continuation is an std::function
|
/* Continuations-related methods. A continuation is an std::function
|
||||||
to be called to finish the execution of a command when running
|
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);
|
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_silent (inferior *inf);
|
||||||
|
|
||||||
extern void exit_inferior_num_silent (int num);
|
extern void exit_inferior_num_silent (int num);
|
||||||
|
|||||||
603
gdb/infrun.c
603
gdb/infrun.c
@@ -104,6 +104,8 @@ static bool start_step_over (void);
|
|||||||
|
|
||||||
static bool step_over_info_valid_p (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
|
/* Asynchronous signal handler registered as event loop source for
|
||||||
when we have pending events ready to be passed to the core. */
|
when we have pending events ready to be passed to the core. */
|
||||||
static struct async_event_handler *infrun_async_inferior_event_token;
|
static struct async_event_handler *infrun_async_inferior_event_token;
|
||||||
@@ -1885,6 +1887,53 @@ displaced_step_prepare (thread_info *thread)
|
|||||||
return status;
|
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
|
/* If we displaced stepped an instruction successfully, adjust registers and
|
||||||
memory to yield the same effect the instruction would have had if we had
|
memory to yield the same effect the instruction would have had if we had
|
||||||
executed it at its original address, and return
|
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. */
|
DISPLACED_STEP_FINISH_STATUS_OK as well. */
|
||||||
|
|
||||||
static displaced_step_finish_status
|
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;
|
displaced_step_thread_state *displaced = &event_thread->displaced_step_state;
|
||||||
|
|
||||||
/* Was this thread performing a displaced step? */
|
/* Was this thread performing a displaced step? */
|
||||||
if (!displaced->in_progress ())
|
if (!displaced->in_progress ())
|
||||||
return DISPLACED_STEP_FINISH_STATUS_OK;
|
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);
|
gdb_assert (event_thread->inf->displaced_step_state.in_progress_count > 0);
|
||||||
event_thread->inf->displaced_step_state.in_progress_count--;
|
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
|
/* Do the fixup, and release the resources acquired to do the displaced
|
||||||
step. */
|
step. */
|
||||||
return gdbarch_displaced_step_finish (displaced->get_original_gdbarch (),
|
displaced_step_finish_status status
|
||||||
event_thread, signal);
|
= 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
|
/* 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
|
else
|
||||||
target_pass_signals (signal_pass);
|
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",
|
infrun_debug_printf ("resume_ptid=%s, step=%d, sig=%s",
|
||||||
resume_ptid.to_string ().c_str (),
|
resume_ptid.to_string ().c_str (),
|
||||||
step, gdb_signal_to_symbol_string (sig));
|
step, gdb_signal_to_symbol_string (sig));
|
||||||
@@ -2452,7 +2626,28 @@ resume_1 (enum gdb_signal sig)
|
|||||||
step = false;
|
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, "
|
infrun_debug_printf ("step=%d, signal=%s, trap_expected=%d, "
|
||||||
"current thread [%s] at %s",
|
"current thread [%s] at %s",
|
||||||
@@ -3933,6 +4128,7 @@ struct wait_one_event
|
|||||||
};
|
};
|
||||||
|
|
||||||
static bool handle_one (const wait_one_event &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.,
|
/* Prepare and stabilize the inferior for detaching it. E.g.,
|
||||||
detaching while a thread is displaced stepping is a recipe for
|
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,
|
/* 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
|
static void
|
||||||
clean_up_just_stopped_threads_fsms (struct execution_control_state *ecs)
|
clean_up_just_stopped_threads_fsms (struct execution_control_state *ecs)
|
||||||
@@ -4140,16 +4341,29 @@ clean_up_just_stopped_threads_fsms (struct execution_control_state *ecs)
|
|||||||
{
|
{
|
||||||
scoped_restore_current_thread restore_thread;
|
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;
|
continue;
|
||||||
|
|
||||||
if (thr == ecs->event_thread)
|
if (thr == ecs->event_thread)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (thr->thread_fsm () != nullptr)
|
||||||
|
{
|
||||||
switch_to_thread (thr);
|
switch_to_thread (thr);
|
||||||
thr->thread_fsm ()->clean_up (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)
|
if (nfds == 0)
|
||||||
{
|
{
|
||||||
/* No waitable targets left. All must be stopped. */
|
/* No waitable targets left. All must be stopped. */
|
||||||
|
infrun_debug_printf ("no waitable targets left");
|
||||||
|
|
||||||
target_waitstatus ws;
|
target_waitstatus ws;
|
||||||
ws.set_no_resumed ();
|
ws.set_no_resumed ();
|
||||||
return {nullptr, minus_one_ptid, std::move (ws)};
|
return {nullptr, minus_one_ptid, std::move (ws)};
|
||||||
@@ -5095,6 +5311,16 @@ handle_one (const wait_one_event &event)
|
|||||||
event.ws);
|
event.ws);
|
||||||
save_waitstatus (t, event.ws);
|
save_waitstatus (t, event.ws);
|
||||||
t->stop_requested = false;
|
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
|
else
|
||||||
@@ -5122,7 +5348,7 @@ handle_one (const wait_one_event &event)
|
|||||||
/* We caught the event that we intended to catch, so
|
/* We caught the event that we intended to catch, so
|
||||||
there's no event to save as pending. */
|
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)
|
== DISPLACED_STEP_FINISH_STATUS_NOT_EXECUTED)
|
||||||
{
|
{
|
||||||
/* Add it back to the step-over queue. */
|
/* Add it back to the step-over queue. */
|
||||||
@@ -5137,7 +5363,6 @@ handle_one (const wait_one_event &event)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
enum gdb_signal sig;
|
|
||||||
struct regcache *regcache;
|
struct regcache *regcache;
|
||||||
|
|
||||||
infrun_debug_printf
|
infrun_debug_printf
|
||||||
@@ -5148,10 +5373,7 @@ handle_one (const wait_one_event &event)
|
|||||||
/* Record for later. */
|
/* Record for later. */
|
||||||
save_waitstatus (t, event.ws);
|
save_waitstatus (t, event.ws);
|
||||||
|
|
||||||
sig = (event.ws.kind () == TARGET_WAITKIND_STOPPED
|
if (displaced_step_finish (t, event.ws)
|
||||||
? event.ws.sig () : GDB_SIGNAL_0);
|
|
||||||
|
|
||||||
if (displaced_step_finish (t, sig)
|
|
||||||
== DISPLACED_STEP_FINISH_STATUS_NOT_EXECUTED)
|
== DISPLACED_STEP_FINISH_STATUS_NOT_EXECUTED)
|
||||||
{
|
{
|
||||||
/* Add it back to the step-over queue. */
|
/* Add it back to the step-over queue. */
|
||||||
@@ -5174,6 +5396,83 @@ handle_one (const wait_one_event &event)
|
|||||||
return false;
|
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. */
|
/* See infrun.h. */
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -5300,6 +5599,8 @@ stop_all_threads (const char *reason, inferior *inf)
|
|||||||
if (pass > 0)
|
if (pass > 0)
|
||||||
pass = -1;
|
pass = -1;
|
||||||
|
|
||||||
|
reenable_target_async ();
|
||||||
|
|
||||||
for (int i = 0; i < waits_needed; i++)
|
for (int i = 0; i < waits_needed; i++)
|
||||||
{
|
{
|
||||||
wait_one_event event = wait_one ();
|
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
|
static bool
|
||||||
handle_no_resumed (struct execution_control_state *ecs)
|
handle_no_resumed (struct execution_control_state *ecs)
|
||||||
@@ -5438,6 +5741,139 @@ handle_no_resumed (struct execution_control_state *ecs)
|
|||||||
return false;
|
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
|
/* Given an execution control state that has been freshly filled in by
|
||||||
an event from the inferior, figure out what it means and take
|
an event from the inferior, figure out what it means and take
|
||||||
appropriate action.
|
appropriate action.
|
||||||
@@ -5476,12 +5912,6 @@ handle_inferior_event (struct execution_control_state *ecs)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ecs->ws.kind () == TARGET_WAITKIND_THREAD_EXITED)
|
|
||||||
{
|
|
||||||
prepare_to_wait (ecs);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ecs->ws.kind () == TARGET_WAITKIND_NO_RESUMED
|
if (ecs->ws.kind () == TARGET_WAITKIND_NO_RESUMED
|
||||||
&& handle_no_resumed (ecs))
|
&& handle_no_resumed (ecs))
|
||||||
return;
|
return;
|
||||||
@@ -5496,7 +5926,6 @@ handle_inferior_event (struct execution_control_state *ecs)
|
|||||||
{
|
{
|
||||||
/* No unwaited-for children left. IOW, all resumed children
|
/* No unwaited-for children left. IOW, all resumed children
|
||||||
have exited. */
|
have exited. */
|
||||||
stop_print_frame = false;
|
|
||||||
stop_waiting (ecs);
|
stop_waiting (ecs);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -5645,6 +6074,12 @@ handle_inferior_event (struct execution_control_state *ecs)
|
|||||||
keep_going (ecs);
|
keep_going (ecs);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case TARGET_WAITKIND_THREAD_EXITED:
|
||||||
|
if (handle_thread_exited (ecs))
|
||||||
|
return;
|
||||||
|
stop_waiting (ecs);
|
||||||
|
break;
|
||||||
|
|
||||||
case TARGET_WAITKIND_EXITED:
|
case TARGET_WAITKIND_EXITED:
|
||||||
case TARGET_WAITKIND_SIGNALLED:
|
case TARGET_WAITKIND_SIGNALLED:
|
||||||
{
|
{
|
||||||
@@ -5720,68 +6155,14 @@ handle_inferior_event (struct execution_control_state *ecs)
|
|||||||
|
|
||||||
case TARGET_WAITKIND_FORKED:
|
case TARGET_WAITKIND_FORKED:
|
||||||
case TARGET_WAITKIND_VFORKED:
|
case TARGET_WAITKIND_VFORKED:
|
||||||
/* Check whether the inferior is displaced stepping. */
|
case TARGET_WAITKIND_THREAD_CLONED:
|
||||||
{
|
|
||||||
struct regcache *regcache = get_thread_regcache (ecs->event_thread);
|
|
||||||
struct gdbarch *gdbarch = regcache->arch ();
|
|
||||||
inferior *parent_inf = find_inferior_ptid (ecs->target, ecs->ptid);
|
|
||||||
|
|
||||||
/* If this is a fork (child gets its own address space copy)
|
displaced_step_finish (ecs->event_thread, ecs->ws);
|
||||||
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
|
/* Start a new step-over in another thread if there's one that
|
||||||
events must supply an implementation of
|
needs it. */
|
||||||
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 ();
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context_switch (ecs);
|
context_switch (ecs);
|
||||||
|
|
||||||
/* Immediately detach breakpoints from the child before there's
|
/* Immediately detach breakpoints from the child before there's
|
||||||
@@ -5796,7 +6177,7 @@ handle_inferior_event (struct execution_control_state *ecs)
|
|||||||
need to unpatch at follow/detach time instead to be certain
|
need to unpatch at follow/detach time instead to be certain
|
||||||
that new breakpoints added between catchpoint hit time and
|
that new breakpoints added between catchpoint hit time and
|
||||||
vfork follow are detached. */
|
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
|
/* This won't actually modify the breakpoint list, but will
|
||||||
physically remove the breakpoints from the child. */
|
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))
|
if (!bpstat_causes_stop (ecs->event_thread->control.stop_bpstat))
|
||||||
{
|
{
|
||||||
bool follow_child
|
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);
|
ecs->event_thread->set_stop_signal (GDB_SIGNAL_0);
|
||||||
|
|
||||||
process_stratum_target *targ
|
process_stratum_target *targ
|
||||||
= ecs->event_thread->inf->process_target ();
|
= 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,
|
/* Note that one of these may be an invalid pointer,
|
||||||
depending on detach_fork. */
|
depending on detach_fork. */
|
||||||
@@ -5846,16 +6237,26 @@ handle_inferior_event (struct execution_control_state *ecs)
|
|||||||
child is marked stopped. */
|
child is marked stopped. */
|
||||||
|
|
||||||
/* If not resuming the parent, mark it 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);
|
parent->set_running (false);
|
||||||
|
|
||||||
/* If resuming the child, mark it running. */
|
/* 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);
|
child->set_running (true);
|
||||||
|
|
||||||
/* In non-stop mode, also resume the other branch. */
|
/* In non-stop mode, also resume the other branch. */
|
||||||
if (!detach_fork && (non_stop
|
if ((ecs->ws.kind () == TARGET_WAITKIND_THREAD_CLONED
|
||||||
|| (sched_multi && target_is_non_stop_p ())))
|
&& 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)
|
if (follow_child)
|
||||||
switch_to_thread (parent);
|
switch_to_thread (parent);
|
||||||
@@ -6118,7 +6519,7 @@ resumed_thread_with_pending_status (struct thread_info *tp,
|
|||||||
static int
|
static int
|
||||||
finish_step_over (struct execution_control_state *ecs)
|
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 ();
|
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. */
|
back an event. */
|
||||||
gdb_assert (ecs->event_thread->control.trap_expected);
|
gdb_assert (ecs->event_thread->control.trap_expected);
|
||||||
|
|
||||||
|
update_thread_events_after_step_over (ecs->event_thread, ecs->ws);
|
||||||
|
|
||||||
clear_step_over_info ();
|
clear_step_over_info ();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6173,6 +6576,13 @@ finish_step_over (struct execution_control_state *ecs)
|
|||||||
if (ecs->event_thread->stepping_over_watchpoint)
|
if (ecs->event_thread->stepping_over_watchpoint)
|
||||||
return 0;
|
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,
|
pending = iterate_over_threads (resumed_thread_with_pending_status,
|
||||||
nullptr);
|
nullptr);
|
||||||
if (pending != nullptr)
|
if (pending != nullptr)
|
||||||
@@ -8753,7 +9163,8 @@ normal_stop ()
|
|||||||
if (inferior_ptid != null_ptid)
|
if (inferior_ptid != null_ptid)
|
||||||
finish_ptid = ptid_t (inferior_ptid.pid ());
|
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;
|
finish_ptid = inferior_ptid;
|
||||||
|
|
||||||
gdb::optional<scoped_finish_thread_state> maybe_finish_thread_state;
|
gdb::optional<scoped_finish_thread_state> maybe_finish_thread_state;
|
||||||
@@ -8796,7 +9207,8 @@ normal_stop ()
|
|||||||
{
|
{
|
||||||
if ((last.kind () != TARGET_WAITKIND_SIGNALLED
|
if ((last.kind () != TARGET_WAITKIND_SIGNALLED
|
||||||
&& last.kind () != TARGET_WAITKIND_EXITED
|
&& 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 ()
|
&& target_has_execution ()
|
||||||
&& previous_thread != inferior_thread ())
|
&& previous_thread != inferior_thread ())
|
||||||
{
|
{
|
||||||
@@ -8812,13 +9224,21 @@ normal_stop ()
|
|||||||
update_previous_thread ();
|
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 ()
|
SWITCH_THRU_ALL_UIS ()
|
||||||
if (current_ui->prompt_state == PROMPT_BLOCKED)
|
if (current_ui->prompt_state == PROMPT_BLOCKED)
|
||||||
{
|
{
|
||||||
target_terminal::ours_for_output ();
|
target_terminal::ours_for_output ();
|
||||||
|
if (last.kind () == TARGET_WAITKIND_NO_RESUMED)
|
||||||
gdb_printf (_("No unwaited-for children left.\n"));
|
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
|
if (last.kind () != TARGET_WAITKIND_SIGNALLED
|
||||||
&& last.kind () != TARGET_WAITKIND_EXITED
|
&& 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 the breakpoint we stopped at, if it wants to be deleted.
|
||||||
Delete any breakpoint that is to be deleted at the next stop. */
|
Delete any breakpoint that is to be deleted at the next stop. */
|
||||||
breakpoint_auto_delete (inferior_thread ()->control.stop_bpstat);
|
breakpoint_auto_delete (inferior_thread ()->control.stop_bpstat);
|
||||||
|
|||||||
362
gdb/linux-nat.c
362
gdb/linux-nat.c
@@ -255,6 +255,31 @@ is_leader (lwp_info *lp)
|
|||||||
return lp->ptid.pid () == lp->ptid.lwp ();
|
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. */
|
/* LWP accessors. */
|
||||||
|
|
||||||
@@ -885,19 +910,16 @@ linux_nat_switch_fork (ptid_t new_ptid)
|
|||||||
registers_changed ();
|
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
|
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 (del_thread)
|
||||||
|
|
||||||
if (th)
|
|
||||||
{
|
{
|
||||||
if (print_thread_events)
|
thread_info *th = find_thread_ptid (linux_target, lp->ptid);
|
||||||
gdb_printf (_("[%s exited]\n"),
|
if (th != nullptr)
|
||||||
target_pid_to_str (lp->ptid).c_str ());
|
|
||||||
|
|
||||||
delete_thread (th);
|
delete_thread (th);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1273,6 +1295,63 @@ get_detach_signal (struct lwp_info *lp)
|
|||||||
return 0;
|
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
|
/* 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.
|
signal number that should be passed to the LWP when detaching.
|
||||||
Otherwise pass any pending signal the LWP may have, if any. */
|
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 lwpid = lp->ptid.lwp ();
|
||||||
int signo;
|
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,
|
gdb::optional<target_waitstatus> ws = get_pending_child_status (lp);
|
||||||
there is a process GDB is attached to that the core of GDB doesn't know
|
if (ws.has_value ())
|
||||||
about. Detach from it. */
|
detach_one_pid (ws->child_ptid ().lwp (), 0);
|
||||||
|
|
||||||
/* 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 ());
|
|
||||||
|
|
||||||
/* If there is a pending SIGSTOP, get rid of it. */
|
/* If there is a pending SIGSTOP, get rid of it. */
|
||||||
if (lp->signalled)
|
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? */
|
this thread with a signal? */
|
||||||
gdb_assert (signo == GDB_SIGNAL_0);
|
gdb_assert (signo == GDB_SIGNAL_0);
|
||||||
|
|
||||||
linux_nat_debug_printf ("Short circuiting for status 0x%x",
|
linux_nat_debug_printf ("Short circuiting for status %s",
|
||||||
lp->status);
|
pending_status_str (lp).c_str ());
|
||||||
|
|
||||||
if (target_can_async_p ())
|
if (target_can_async_p ())
|
||||||
{
|
{
|
||||||
@@ -1808,6 +1846,55 @@ linux_handle_syscall_trap (struct lwp_info *lp, int stopping)
|
|||||||
return 1;
|
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
|
/* 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
|
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
|
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);
|
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)
|
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
|
/* The arch-specific native code may need to know about new
|
||||||
forks even if those end up never mapped to an
|
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)
|
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)
|
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)
|
else if (event == PTRACE_EVENT_CLONE)
|
||||||
{
|
{
|
||||||
struct lwp_info *new_lp;
|
|
||||||
|
|
||||||
ourstatus->set_ignore ();
|
|
||||||
|
|
||||||
linux_nat_debug_printf
|
linux_nat_debug_printf
|
||||||
("Got clone event from LWP %d, new child is LWP %ld", pid, new_pid);
|
("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));
|
/* Save the status again, we'll use it in follow_clone. */
|
||||||
new_lp->stopped = 1;
|
add_to_pid_list (&stopped_pids, new_pid, status);
|
||||||
new_lp->resumed = 1;
|
|
||||||
|
|
||||||
/* If the thread_db layer is active, let it record the user
|
ourstatus->set_thread_cloned (ptid_t (lp->ptid.pid (), new_pid));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
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
|
thread execs, it changes its tid to the tgid, and the old
|
||||||
tgid thread might have not been resumed. */
|
tgid thread might have not been resumed. */
|
||||||
lp->resumed = 1;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2089,8 +2141,7 @@ wait_lwp (struct lwp_info *lp)
|
|||||||
/* Check if the thread has exited. */
|
/* Check if the thread has exited. */
|
||||||
if (WIFEXITED (status) || WIFSIGNALED (status))
|
if (WIFEXITED (status) || WIFSIGNALED (status))
|
||||||
{
|
{
|
||||||
if (report_thread_events
|
if (report_exit_events_for (lp) || is_leader (lp))
|
||||||
|| lp->ptid.pid () == lp->ptid.lwp ())
|
|
||||||
{
|
{
|
||||||
linux_nat_debug_printf ("LWP %d exited.", lp->ptid.pid ());
|
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. */
|
/* Check if the thread has exited. */
|
||||||
if (WIFEXITED (status) || WIFSIGNALED (status))
|
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.",
|
linux_nat_debug_printf ("%s exited.",
|
||||||
lp->ptid.to_string ().c_str ());
|
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
|
/* Convenience function that is called when we're about to return an
|
||||||
event. This decides whether to report the event to GDB as a
|
event to the core. If the event is an exit or signalled event,
|
||||||
process exit event, a thread exit event, or to suppress the
|
then this decides whether to report it as process-wide event, as a
|
||||||
event. */
|
thread exit event, or to suppress it. All other event kinds are
|
||||||
|
passed through unmodified. */
|
||||||
|
|
||||||
static ptid_t
|
static ptid_t
|
||||||
filter_exit_event (struct lwp_info *event_child,
|
filter_exit_event (struct lwp_info *event_child,
|
||||||
@@ -3092,15 +3144,29 @@ filter_exit_event (struct lwp_info *event_child,
|
|||||||
{
|
{
|
||||||
ptid_t ptid = event_child->ptid;
|
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 (!is_leader (event_child))
|
||||||
{
|
{
|
||||||
if (report_thread_events)
|
if (report_exit_events_for (event_child))
|
||||||
|
{
|
||||||
ourstatus->set_thread_exited (0);
|
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
|
else
|
||||||
|
{
|
||||||
ourstatus->set_ignore ();
|
ourstatus->set_ignore ();
|
||||||
|
|
||||||
exit_lwp (event_child);
|
exit_lwp (event_child);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ptid;
|
return ptid;
|
||||||
}
|
}
|
||||||
@@ -3137,7 +3203,7 @@ linux_nat_wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus,
|
|||||||
if (lp != NULL)
|
if (lp != NULL)
|
||||||
{
|
{
|
||||||
linux_nat_debug_printf ("Using pending wait status %s for %s.",
|
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 ());
|
lp->ptid.to_string ().c_str ());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3321,10 +3387,7 @@ linux_nat_wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus,
|
|||||||
else
|
else
|
||||||
lp->core = linux_common_core_of_thread (lp->ptid);
|
lp->core = linux_common_core_of_thread (lp->ptid);
|
||||||
|
|
||||||
if (ourstatus->kind () == TARGET_WAITKIND_EXITED)
|
|
||||||
return filter_exit_event (lp, ourstatus);
|
return filter_exit_event (lp, ourstatus);
|
||||||
|
|
||||||
return lp->ptid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Resume LWPs that are currently stopped without any pending status
|
/* Resume LWPs that are currently stopped without any pending status
|
||||||
@@ -3508,18 +3571,13 @@ kill_wait_callback (struct lwp_info *lp)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Kill the fork children of any threads of inferior INF that are
|
/* Kill the fork/clone child of LP if it has an unfollowed child. */
|
||||||
stopped at a fork event. */
|
|
||||||
|
|
||||||
static void
|
static int
|
||||||
kill_unfollowed_fork_children (struct inferior *inf)
|
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;
|
|
||||||
|
|
||||||
if (ws->kind () == TARGET_WAITKIND_FORKED
|
|
||||||
|| ws->kind () == TARGET_WAITKIND_VFORKED)
|
|
||||||
{
|
{
|
||||||
ptid_t child_ptid = ws->child_ptid ();
|
ptid_t child_ptid = ws->child_ptid ();
|
||||||
int child_pid = child_ptid.pid ();
|
int child_pid = child_ptid.pid ();
|
||||||
@@ -3530,37 +3588,39 @@ kill_unfollowed_fork_children (struct inferior *inf)
|
|||||||
|
|
||||||
/* Let the arch-specific native code know this process is
|
/* Let the arch-specific native code know this process is
|
||||||
gone. */
|
gone. */
|
||||||
|
if (ws->kind () != TARGET_WAITKIND_THREAD_CLONED)
|
||||||
linux_target->low_forget_process (child_pid);
|
linux_target->low_forget_process (child_pid);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
linux_nat_target::kill ()
|
linux_nat_target::kill ()
|
||||||
{
|
{
|
||||||
/* If we're stopped while forking and we haven't followed yet,
|
ptid_t pid_ptid (inferior_ptid.pid ());
|
||||||
kill the other task. We need to do this first because the
|
|
||||||
|
/* 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. */
|
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 ())
|
if (forks_exist_p ())
|
||||||
linux_fork_killall ();
|
linux_fork_killall ();
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ptid_t ptid = ptid_t (inferior_ptid.pid ());
|
|
||||||
|
|
||||||
/* Stop all threads before killing them, since ptrace requires
|
/* Stop all threads before killing them, since ptrace requires
|
||||||
that the thread is stopped to successfully PTRACE_KILL. */
|
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
|
/* ... and wait until all of them have reported back that
|
||||||
they're no longer running. */
|
they're no longer running. */
|
||||||
iterate_over_lwps (ptid, stop_wait_callback);
|
iterate_over_lwps (pid_ptid, stop_wait_callback);
|
||||||
|
|
||||||
/* Kill all LWP's ... */
|
/* 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. */
|
/* ... 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);
|
target_mourn_inferior (inferior_ptid);
|
||||||
@@ -4427,6 +4487,14 @@ linux_nat_target::thread_events (int enable)
|
|||||||
report_thread_events = 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 ()
|
linux_nat_target::linux_nat_target ()
|
||||||
{
|
{
|
||||||
/* We don't change the stratum; this target will sit at
|
/* We don't change the stratum; this target will sit at
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ public:
|
|||||||
|
|
||||||
void thread_events (int) override;
|
void thread_events (int) override;
|
||||||
|
|
||||||
|
bool supports_set_thread_options (gdb_thread_options options) override;
|
||||||
|
|
||||||
bool can_async_p () override;
|
bool can_async_p () override;
|
||||||
|
|
||||||
bool supports_non_stop () override;
|
bool supports_non_stop () override;
|
||||||
@@ -129,6 +131,8 @@ public:
|
|||||||
|
|
||||||
void follow_fork (inferior *, ptid_t, target_waitkind, bool, bool) override;
|
void follow_fork (inferior *, ptid_t, target_waitkind, bool, bool) override;
|
||||||
|
|
||||||
|
void follow_clone (ptid_t) override;
|
||||||
|
|
||||||
std::vector<static_tracepoint_marker>
|
std::vector<static_tracepoint_marker>
|
||||||
static_tracepoint_markers_by_strid (const char *id) override;
|
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. */
|
/* The last resume GDB requested on this thread. */
|
||||||
resume_kind last_resume_kind = resume_continue;
|
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;
|
int status = 0;
|
||||||
|
|
||||||
/* When 'stopped' is set, this is where the lwp last stopped, with
|
/* 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. */
|
/* Non-zero if we expect a duplicated SIGINT. */
|
||||||
int ignore_sigint = 0;
|
int ignore_sigint = 0;
|
||||||
|
|
||||||
/* If WAITSTATUS->KIND != TARGET_WAITKIND_SPURIOUS, the waitstatus
|
/* If WAITSTATUS->KIND != TARGET_WAITKIND_IGNORE, the waitstatus for
|
||||||
for this LWP's last event. This may correspond to STATUS above,
|
this LWP's last event. This usually corresponds to STATUS above,
|
||||||
or to a local variable in lin_lwp_wait. */
|
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;
|
struct target_waitstatus waitstatus;
|
||||||
|
|
||||||
/* Signal whether we are in a SYSCALL_ENTRY or
|
/* 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. */
|
/* See linux-tdep.h. */
|
||||||
|
|
||||||
displaced_step_finish_status
|
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);
|
linux_info *per_inferior = get_linux_inferior_data (thread->inf);
|
||||||
|
|
||||||
gdb_assert (per_inferior->disp_step_bufs.has_value ());
|
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. */
|
/* See linux-tdep.h. */
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ extern displaced_step_prepare_status linux_displaced_step_prepare
|
|||||||
/* Implementation of gdbarch_displaced_step_finish. */
|
/* Implementation of gdbarch_displaced_step_finish. */
|
||||||
|
|
||||||
extern displaced_step_finish_status linux_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. */
|
/* 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_on_no_history (void);
|
||||||
|
|
||||||
static void mi_new_thread (struct thread_info *t);
|
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 *,
|
static void mi_record_changed (struct inferior*, int, const char *,
|
||||||
const char *);
|
const char *);
|
||||||
static void mi_inferior_added (struct inferior *inf);
|
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
|
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 ()
|
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. */
|
/* NetBSD does not store an LWP exit status. */
|
||||||
ourstatus->set_thread_exited (0);
|
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. */
|
/* 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. */
|
/* The thread specified by T has been created. */
|
||||||
extern observable<struct thread_info */* t */> new_thread;
|
extern observable<struct thread_info */* t */> new_thread;
|
||||||
|
|
||||||
/* The thread specified by T has exited. The SILENT argument
|
/* The thread specified by T has exited. EXIT_CODE is the thread's
|
||||||
indicates that gdb is removing the thread from its tables without
|
exit code, if available. The SILENT argument indicates that GDB is
|
||||||
wanting to notify the user about it. */
|
removing the thread from its tables without wanting to notify the
|
||||||
extern observable<struct thread_info */* t */, int /* silent */> thread_exit;
|
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
|
/* An explicit stop request was issued to PTID. If PTID equals
|
||||||
minus_one_ptid, the request applied to all threads. If
|
minus_one_ptid, the request applied to all threads. If
|
||||||
|
|||||||
@@ -2115,9 +2115,6 @@ wait_again:
|
|||||||
case PR_SYSENTRY:
|
case PR_SYSENTRY:
|
||||||
if (what == SYS_lwp_exit)
|
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));
|
delete_thread (find_thread_ptid (this, retval));
|
||||||
target_continue_no_signal (ptid);
|
target_continue_no_signal (ptid);
|
||||||
goto wait_again;
|
goto wait_again;
|
||||||
@@ -2222,9 +2219,6 @@ wait_again:
|
|||||||
}
|
}
|
||||||
else if (what == SYS_lwp_exit)
|
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));
|
delete_thread (find_thread_ptid (this, retval));
|
||||||
status->set_spurious ();
|
status->set_spurious ();
|
||||||
return retval;
|
return retval;
|
||||||
|
|||||||
@@ -360,7 +360,9 @@ add_thread_object (struct thread_info *tp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
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)
|
if (!gdb_python_initialized)
|
||||||
return;
|
return;
|
||||||
|
|||||||
301
gdb/remote.c
301
gdb/remote.c
@@ -248,6 +248,9 @@ enum {
|
|||||||
/* Support for the QThreadEvents packet. */
|
/* Support for the QThreadEvents packet. */
|
||||||
PACKET_QThreadEvents,
|
PACKET_QThreadEvents,
|
||||||
|
|
||||||
|
/* Support for the QThreadOptions packet. */
|
||||||
|
PACKET_QThreadOptions,
|
||||||
|
|
||||||
/* Support for multi-process extensions. */
|
/* Support for multi-process extensions. */
|
||||||
PACKET_multiprocess_feature,
|
PACKET_multiprocess_feature,
|
||||||
|
|
||||||
@@ -489,6 +492,10 @@ public: /* data */
|
|||||||
the target know about program signals list changes. */
|
the target know about program signals list changes. */
|
||||||
char *last_program_signals_packet = nullptr;
|
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;
|
gdb_signal last_sent_signal = GDB_SIGNAL_0;
|
||||||
|
|
||||||
bool last_sent_step = false;
|
bool last_sent_step = false;
|
||||||
@@ -556,6 +563,10 @@ public: /* data */
|
|||||||
this can go away. */
|
this can go away. */
|
||||||
int wait_forever_enabled_p = 1;
|
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:
|
private:
|
||||||
/* Mapping of remote protocol data for each gdbarch. Usually there
|
/* Mapping of remote protocol data for each gdbarch. Usually there
|
||||||
is only one entry here, though we may see more with stubs that
|
is only one entry here, though we may see more with stubs that
|
||||||
@@ -716,6 +727,8 @@ public:
|
|||||||
void detach (inferior *, int) override;
|
void detach (inferior *, int) override;
|
||||||
void disconnect (const char *, int) override;
|
void disconnect (const char *, int) override;
|
||||||
|
|
||||||
|
void commit_requested_thread_options ();
|
||||||
|
|
||||||
void commit_resumed () override;
|
void commit_resumed () override;
|
||||||
void resume (ptid_t, int, enum gdb_signal) override;
|
void resume (ptid_t, int, enum gdb_signal) override;
|
||||||
ptid_t wait (ptid_t, struct target_waitstatus *, target_wait_flags) override;
|
ptid_t wait (ptid_t, struct target_waitstatus *, target_wait_flags) override;
|
||||||
@@ -842,6 +855,8 @@ public:
|
|||||||
|
|
||||||
void thread_events (int) override;
|
void thread_events (int) override;
|
||||||
|
|
||||||
|
bool supports_set_thread_options (gdb_thread_options) override;
|
||||||
|
|
||||||
int can_do_single_step () override;
|
int can_do_single_step () override;
|
||||||
|
|
||||||
void terminal_inferior () override;
|
void terminal_inferior () override;
|
||||||
@@ -979,6 +994,7 @@ public:
|
|||||||
const struct btrace_config *btrace_conf (const struct btrace_target_info *) override;
|
const struct btrace_config *btrace_conf (const struct btrace_target_info *) override;
|
||||||
bool augmented_libraries_svr4_read () override;
|
bool augmented_libraries_svr4_read () override;
|
||||||
void follow_fork (inferior *, ptid_t, target_waitkind, bool, bool) 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;
|
void follow_exec (inferior *, ptid_t, const char *) override;
|
||||||
int insert_fork_catchpoint (int) override;
|
int insert_fork_catchpoint (int) override;
|
||||||
int remove_fork_catchpoint (int) override;
|
int remove_fork_catchpoint (int) override;
|
||||||
@@ -1072,7 +1088,7 @@ public: /* Remote specific methods. */
|
|||||||
|
|
||||||
void remote_btrace_maybe_reopen ();
|
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 kill_new_fork_children (inferior *inf);
|
||||||
void discard_pending_stop_replies (struct inferior *inf);
|
void discard_pending_stop_replies (struct inferior *inf);
|
||||||
int stop_reply_queue_length ();
|
int stop_reply_queue_length ();
|
||||||
@@ -1139,6 +1155,9 @@ public: /* Remote specific methods. */
|
|||||||
|
|
||||||
void remote_packet_size (const protocol_feature *feature,
|
void remote_packet_size (const protocol_feature *feature,
|
||||||
packet_support support, const char *value);
|
packet_support support, const char *value);
|
||||||
|
void remote_supported_thread_options (const protocol_feature *feature,
|
||||||
|
enum packet_support support,
|
||||||
|
const char *value);
|
||||||
|
|
||||||
void remote_serial_quit_handler ();
|
void remote_serial_quit_handler ();
|
||||||
|
|
||||||
@@ -2755,8 +2774,9 @@ remote_target::remote_add_thread (ptid_t ptid, bool running, bool executing,
|
|||||||
else
|
else
|
||||||
thread = add_thread (this, ptid);
|
thread = add_thread (this, ptid);
|
||||||
|
|
||||||
/* We start by assuming threads are resumed. That state then gets updated
|
/* We start by assuming threads are resumed. That state then gets
|
||||||
when we process a matching stop reply. */
|
updated when we process a matching stop reply. */
|
||||||
|
if (executing)
|
||||||
get_remote_thread_info (thread)->set_resumed ();
|
get_remote_thread_info (thread)->set_resumed ();
|
||||||
|
|
||||||
set_executing (this, ptid, executing);
|
set_executing (this, ptid, executing);
|
||||||
@@ -4152,15 +4172,25 @@ remote_target::update_thread_list ()
|
|||||||
if (has_single_non_exited_thread (tp->inf))
|
if (has_single_non_exited_thread (tp->inf))
|
||||||
continue;
|
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. */
|
/* Not found. */
|
||||||
delete_thread (tp);
|
delete_thread (tp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove any unreported fork child threads from CONTEXT so
|
/* Remove any unreported fork/vfork/clone child threads from
|
||||||
that we don't interfere with follow fork, which is where
|
CONTEXT so that we don't interfere with follow
|
||||||
creation of such threads is handled. */
|
fork/vfork/clone, which is where creation of such threads is
|
||||||
remove_new_fork_children (&context);
|
handled. */
|
||||||
|
remove_new_children (&context);
|
||||||
|
|
||||||
/* And now add threads we don't know about yet to our list. */
|
/* And now add threads we don't know about yet to our list. */
|
||||||
for (thread_item &item : context.items)
|
for (thread_item &item : context.items)
|
||||||
@@ -5124,6 +5154,8 @@ remote_target::start_remote_1 (int from_tty, int extended_p)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
switch_to_thread (find_thread_ptid (this, curr_thread));
|
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
|
/* init_wait_for_inferior should be called before get_offsets in order
|
||||||
@@ -5462,7 +5494,8 @@ remote_supported_packet (remote_target *remote,
|
|||||||
|
|
||||||
void
|
void
|
||||||
remote_target::remote_packet_size (const protocol_feature *feature,
|
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 ();
|
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);
|
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[] = {
|
static const struct protocol_feature remote_protocol_features[] = {
|
||||||
{ "PacketSize", PACKET_DISABLE, remote_packet_size, -1 },
|
{ "PacketSize", PACKET_DISABLE, remote_packet_size, -1 },
|
||||||
{ "qXfer:auxv:read", PACKET_DISABLE, remote_supported_packet,
|
{ "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 },
|
PACKET_Qbtrace_conf_pt_size },
|
||||||
{ "vContSupported", PACKET_DISABLE, remote_supported_packet, PACKET_vContSupported },
|
{ "vContSupported", PACKET_DISABLE, remote_supported_packet, PACKET_vContSupported },
|
||||||
{ "QThreadEvents", PACKET_DISABLE, remote_supported_packet, PACKET_QThreadEvents },
|
{ "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 },
|
{ "no-resumed", PACKET_DISABLE, remote_supported_packet, PACKET_no_resumed },
|
||||||
{ "memory-tagging", PACKET_DISABLE, remote_supported_packet,
|
{ "memory-tagging", PACKET_DISABLE, remote_supported_packet,
|
||||||
PACKET_memory_tagging_feature },
|
PACKET_memory_tagging_feature },
|
||||||
@@ -5703,6 +5781,10 @@ remote_target::remote_query_supported ()
|
|||||||
!= AUTO_BOOLEAN_FALSE)
|
!= AUTO_BOOLEAN_FALSE)
|
||||||
remote_query_supported_append (&q, "QThreadEvents+");
|
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)
|
if (m_features.packet_set_cmd_state (PACKET_no_resumed)
|
||||||
!= AUTO_BOOLEAN_FALSE)
|
!= AUTO_BOOLEAN_FALSE)
|
||||||
remote_query_supported_append (&q, "no-resumed+");
|
remote_query_supported_append (&q, "no-resumed+");
|
||||||
@@ -6083,16 +6165,25 @@ is_fork_status (target_waitkind kind)
|
|||||||
|| kind == TARGET_WAITKIND_VFORKED);
|
|| kind == TARGET_WAITKIND_VFORKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return THREAD's pending status if it is a pending fork parent, else
|
/* Return a reference to the field where a pending child status, if
|
||||||
return nullptr. */
|
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 *
|
static const target_waitstatus *
|
||||||
thread_pending_fork_status (struct thread_info *thread)
|
thread_pending_fork_status (struct thread_info *thread)
|
||||||
{
|
{
|
||||||
const target_waitstatus &ws
|
const target_waitstatus &ws = thread_pending_status (thread);
|
||||||
= (thread->has_pending_waitstatus ()
|
|
||||||
? thread->pending_waitstatus ()
|
|
||||||
: thread->pending_follow);
|
|
||||||
|
|
||||||
if (!is_fork_status (ws.kind ()))
|
if (!is_fork_status (ws.kind ()))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -6100,6 +6191,20 @@ thread_pending_fork_status (struct thread_info *thread)
|
|||||||
return &ws;
|
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. */
|
/* Detach the specified process. */
|
||||||
|
|
||||||
void
|
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
|
/* Target follow-exec function for remote targets. Save EXECD_PATHNAME
|
||||||
in the program space of the new inferior. */
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
commit_requested_thread_options ();
|
||||||
|
|
||||||
/* In all-stop, we can't mark REMOTE_ASYNC_GET_PENDING_EVENTS_TOKEN
|
/* In all-stop, we can't mark REMOTE_ASYNC_GET_PENDING_EVENTS_TOKEN
|
||||||
(explained in remote-notif.c:handle_notification) so
|
(explained in remote-notif.c:handle_notification) so
|
||||||
remote_notif_process is not called. We need find a place where
|
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)
|
if (!target_is_non_stop_p () || ::execution_direction == EXEC_REVERSE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
commit_requested_thread_options ();
|
||||||
|
|
||||||
/* Try to send wildcard actions ("vCont;c" or "vCont;c:pPID.-1")
|
/* Try to send wildcard actions ("vCont;c" or "vCont;c:pPID.-1")
|
||||||
instead of resuming all threads of each process individually.
|
instead of resuming all threads of each process individually.
|
||||||
However, if any thread of a process must remain halted, we can't
|
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)
|
if (priv->get_resume_state () == resume_state::RESUMED_PENDING_VCONT)
|
||||||
any_pending_vcont_resume = true;
|
any_pending_vcont_resume = true;
|
||||||
|
|
||||||
/* If a thread is the parent of an unfollowed fork, then we
|
/* If a thread is the parent of an unfollowed fork/vfork/clone,
|
||||||
can't do a global wildcard, as that would resume the fork
|
then we can't do a global wildcard, as that would resume the
|
||||||
child. */
|
pending child. */
|
||||||
if (thread_pending_fork_status (tp) != nullptr)
|
if (thread_pending_child_status (tp) != nullptr)
|
||||||
may_global_wildcard_vcont = false;
|
may_global_wildcard_vcont = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7455,22 +7570,22 @@ const notif_client notif_client_stop =
|
|||||||
REMOTE_NOTIF_STOP,
|
REMOTE_NOTIF_STOP,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* If CONTEXT contains any fork child threads that have not been
|
/* If CONTEXT contains any fork/vfork/clone child threads that have
|
||||||
reported yet, remove them from the CONTEXT list. If such a
|
not been reported yet, remove them from the CONTEXT list. If such
|
||||||
thread exists it is because we are stopped at a fork catchpoint
|
a thread exists it is because we are stopped at a fork/vfork/clone
|
||||||
and have not yet called follow_fork, which will set up the
|
catchpoint and have not yet called follow_fork/follow_clone, which
|
||||||
host-side data structures for the new process. */
|
will set up the host-side data structures for the new child. */
|
||||||
|
|
||||||
void
|
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;
|
const notif_client *notif = ¬if_client_stop;
|
||||||
|
|
||||||
/* For any threads stopped at a fork event, remove the corresponding
|
/* For any threads stopped at a (v)fork/clone event, remove the
|
||||||
fork child threads from the CONTEXT list. */
|
corresponding child threads from the CONTEXT list. */
|
||||||
for (thread_info *thread : all_non_exited_threads (this))
|
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)
|
if (ws == nullptr)
|
||||||
continue;
|
continue;
|
||||||
@@ -7478,13 +7593,12 @@ remote_target::remove_new_fork_children (threads_listing_context *context)
|
|||||||
context->remove_thread (ws->child_ptid ());
|
context->remove_thread (ws->child_ptid ());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check for any pending fork events (not reported or processed yet)
|
/* Check for any pending (v)fork/clone events (not reported or
|
||||||
in process PID and remove those fork child threads from the
|
processed yet) in process PID and remove those child threads from
|
||||||
CONTEXT list as well. */
|
the CONTEXT list as well. */
|
||||||
remote_notif_get_pending_events (notif);
|
remote_notif_get_pending_events (notif);
|
||||||
for (auto &event : get_remote_state ()->stop_reply_queue)
|
for (auto &event : get_remote_state ()->stop_reply_queue)
|
||||||
if (event->ws.kind () == TARGET_WAITKIND_FORKED
|
if (is_new_child_status (event->ws.kind ()))
|
||||||
|| event->ws.kind () == TARGET_WAITKIND_VFORKED)
|
|
||||||
context->remove_thread (event->ws.child_ptid ());
|
context->remove_thread (event->ws.child_ptid ());
|
||||||
else if (event->ws.kind () == TARGET_WAITKIND_THREAD_EXITED)
|
else if (event->ws.kind () == TARGET_WAITKIND_THREAD_EXITED)
|
||||||
context->remove_thread (event->ptid);
|
context->remove_thread (event->ptid);
|
||||||
@@ -7815,6 +7929,8 @@ Packet: '%s'\n"),
|
|||||||
event->ws.set_forked (read_ptid (++p1, &p));
|
event->ws.set_forked (read_ptid (++p1, &p));
|
||||||
else if (strprefix (p, p1, "vfork"))
|
else if (strprefix (p, p1, "vfork"))
|
||||||
event->ws.set_vforked (read_ptid (++p1, &p));
|
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"))
|
else if (strprefix (p, p1, "vforkdone"))
|
||||||
{
|
{
|
||||||
event->ws.set_vfork_done ();
|
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)
|
&& status->kind () != TARGET_WAITKIND_NO_RESUMED)
|
||||||
{
|
{
|
||||||
/* Expedited registers. */
|
/* Expedited registers. */
|
||||||
if (!stop_reply->regcache.empty ())
|
if (status->kind () != TARGET_WAITKIND_THREAD_EXITED
|
||||||
|
&& !stop_reply->regcache.empty ())
|
||||||
{
|
{
|
||||||
struct regcache *regcache
|
struct regcache *regcache
|
||||||
= get_thread_arch_regcache (this, ptid, stop_reply->arch);
|
= 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. */
|
again. Keep waiting for events. */
|
||||||
rs->waiting_for_stop_reply = 1;
|
rs->waiting_for_stop_reply = 1;
|
||||||
break;
|
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. */
|
/* There is a stop reply to handle. */
|
||||||
rs->waiting_for_stop_reply = 0;
|
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)
|
if (m_features.packet_support (PACKET_QThreadEvents) == PACKET_DISABLE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (rs->last_thread_events == enable)
|
||||||
|
return;
|
||||||
|
|
||||||
xsnprintf (rs->buf.data (), size, "QThreadEvents:%x", enable ? 1 : 0);
|
xsnprintf (rs->buf.data (), size, "QThreadEvents:%x", enable ? 1 : 0);
|
||||||
putpkt (rs->buf);
|
putpkt (rs->buf);
|
||||||
getpkt (&rs->buf, 0);
|
getpkt (&rs->buf, 0);
|
||||||
@@ -14654,6 +14774,7 @@ remote_target::thread_events (int enable)
|
|||||||
case PACKET_OK:
|
case PACKET_OK:
|
||||||
if (strcmp (rs->buf.data (), "OK") != 0)
|
if (strcmp (rs->buf.data (), "OK") != 0)
|
||||||
error (_("Remote refused setting thread events: %s"), rs->buf.data ());
|
error (_("Remote refused setting thread events: %s"), rs->buf.data ());
|
||||||
|
rs->last_thread_events = enable;
|
||||||
break;
|
break;
|
||||||
case PACKET_ERROR:
|
case PACKET_ERROR:
|
||||||
warning (_("Remote failure reply: %s"), rs->buf.data ());
|
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
|
static void
|
||||||
show_remote_cmd (const char *args, int from_tty)
|
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",
|
add_packet_config_cmd (PACKET_QThreadEvents, "QThreadEvents", "thread-events",
|
||||||
0);
|
0);
|
||||||
|
|
||||||
|
add_packet_config_cmd (PACKET_QThreadOptions, "QThreadOptions",
|
||||||
|
"thread-options", 0);
|
||||||
|
|
||||||
add_packet_config_cmd (PACKET_no_resumed, "N stop reply",
|
add_packet_config_cmd (PACKET_no_resumed, "N stop reply",
|
||||||
"no-resumed-stop-reply", 0);
|
"no-resumed-stop-reply", 0);
|
||||||
|
|
||||||
|
|||||||
@@ -1088,13 +1088,13 @@ ppc_displaced_step_prepare (gdbarch *arch, thread_info *thread,
|
|||||||
|
|
||||||
static displaced_step_finish_status
|
static displaced_step_finish_status
|
||||||
ppc_displaced_step_finish (gdbarch *arch, thread_info *thread,
|
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);
|
ppc_inferior_data *per_inferior = get_ppc_per_inferior (thread->inf);
|
||||||
|
|
||||||
gdb_assert (per_inferior->disp_step_buf.has_value ());
|
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. */
|
/* Implementation of gdbarch_displaced_step_restore_all_in_ptid. */
|
||||||
|
|||||||
@@ -176,6 +176,8 @@
|
|||||||
target_debug_do_print (X.get ())
|
target_debug_do_print (X.get ())
|
||||||
#define target_debug_print_target_waitkind(X) \
|
#define target_debug_print_target_waitkind(X) \
|
||||||
target_debug_do_print (pulongest (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
|
static void
|
||||||
target_debug_print_struct_target_waitstatus_p (struct target_waitstatus *status)
|
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 insert_vfork_catchpoint (int arg0) override;
|
||||||
int remove_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_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 insert_exec_catchpoint (int arg0) override;
|
||||||
int remove_exec_catchpoint (int arg0) override;
|
int remove_exec_catchpoint (int arg0) override;
|
||||||
void follow_exec (inferior *arg0, ptid_t arg1, const char *arg2) 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;
|
int async_wait_fd () override;
|
||||||
bool has_pending_events () override;
|
bool has_pending_events () override;
|
||||||
void thread_events (int arg0) override;
|
void thread_events (int arg0) override;
|
||||||
|
bool supports_set_thread_options (gdb_thread_options arg0) override;
|
||||||
bool supports_non_stop () override;
|
bool supports_non_stop () override;
|
||||||
bool always_non_stop_p () override;
|
bool always_non_stop_p () override;
|
||||||
int find_memory_regions (find_memory_region_ftype arg0, void *arg1) 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 insert_vfork_catchpoint (int arg0) override;
|
||||||
int remove_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_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 insert_exec_catchpoint (int arg0) override;
|
||||||
int remove_exec_catchpoint (int arg0) override;
|
int remove_exec_catchpoint (int arg0) override;
|
||||||
void follow_exec (inferior *arg0, ptid_t arg1, const char *arg2) 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;
|
int async_wait_fd () override;
|
||||||
bool has_pending_events () override;
|
bool has_pending_events () override;
|
||||||
void thread_events (int arg0) override;
|
void thread_events (int arg0) override;
|
||||||
|
bool supports_set_thread_options (gdb_thread_options arg0) override;
|
||||||
bool supports_non_stop () override;
|
bool supports_non_stop () override;
|
||||||
bool always_non_stop_p () override;
|
bool always_non_stop_p () override;
|
||||||
int find_memory_regions (find_memory_region_ftype arg0, void *arg1) 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);
|
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
|
int
|
||||||
target_ops::insert_exec_catchpoint (int arg0)
|
target_ops::insert_exec_catchpoint (int arg0)
|
||||||
{
|
{
|
||||||
@@ -2248,6 +2274,32 @@ debug_target::thread_events (int arg0)
|
|||||||
gdb_puts (")\n", gdb_stdlog);
|
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
|
bool
|
||||||
target_ops::supports_non_stop ()
|
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"));
|
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. */
|
/* See target.h. */
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -4351,6 +4358,15 @@ target_thread_events (int enable)
|
|||||||
current_inferior ()->top_target ()->thread_events (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
|
/* Controls if targets can report that they can/are async. This is
|
||||||
just for maintainers to use when debugging gdb. */
|
just for maintainers to use when debugging gdb. */
|
||||||
bool target_async_permitted = true;
|
bool target_async_permitted = true;
|
||||||
|
|||||||
15
gdb/target.h
15
gdb/target.h
@@ -637,6 +637,13 @@ struct target_ops
|
|||||||
TARGET_DEFAULT_RETURN (1);
|
TARGET_DEFAULT_RETURN (1);
|
||||||
virtual void follow_fork (inferior *, ptid_t, target_waitkind, bool, bool)
|
virtual void follow_fork (inferior *, ptid_t, target_waitkind, bool, bool)
|
||||||
TARGET_DEFAULT_FUNC (default_follow_fork);
|
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)
|
virtual int insert_exec_catchpoint (int)
|
||||||
TARGET_DEFAULT_RETURN (1);
|
TARGET_DEFAULT_RETURN (1);
|
||||||
virtual int remove_exec_catchpoint (int)
|
virtual int remove_exec_catchpoint (int)
|
||||||
@@ -729,6 +736,10 @@ struct target_ops
|
|||||||
TARGET_DEFAULT_RETURN (false);
|
TARGET_DEFAULT_RETURN (false);
|
||||||
virtual void thread_events (int)
|
virtual void thread_events (int)
|
||||||
TARGET_DEFAULT_IGNORE ();
|
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
|
/* This method must be implemented in some situations. See the
|
||||||
comment on 'can_run'. */
|
comment on 'can_run'. */
|
||||||
virtual bool supports_non_stop ()
|
virtual bool supports_non_stop ()
|
||||||
@@ -1888,6 +1899,10 @@ extern void target_async (bool enable);
|
|||||||
/* Enables/disables thread create and exit events. */
|
/* Enables/disables thread create and exit events. */
|
||||||
extern void target_thread_events (int enable);
|
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
|
/* Whether support for controlling the target backends always in
|
||||||
non-stop mode is enabled. */
|
non-stop mode is enabled. */
|
||||||
extern enum auto_boolean target_non_stop_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 ());
|
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/waitstatus.h"
|
||||||
#include "target/wait.h"
|
#include "target/wait.h"
|
||||||
|
#include "gdbsupport/enum-flags.h"
|
||||||
|
|
||||||
/* This header is a stopgap until more code is shared. */
|
/* 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
|
/* Read LEN bytes of target memory at address MEMADDR, placing the
|
||||||
results in GDB's memory at MYADDR. Return zero for success,
|
results in GDB's memory at MYADDR. Return zero for success,
|
||||||
nonzero if any error occurs. This function must be provided by
|
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_FORKED:
|
||||||
case TARGET_WAITKIND_VFORKED:
|
case TARGET_WAITKIND_VFORKED:
|
||||||
|
case TARGET_WAITKIND_THREAD_CLONED:
|
||||||
return string_appendf (str, ", child_ptid = %s",
|
return string_appendf (str, ", child_ptid = %s",
|
||||||
this->child_ptid ().to_string ().c_str ());
|
this->child_ptid ().to_string ().c_str ());
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,13 @@ enum target_waitkind
|
|||||||
/* There are no resumed children left in the program. */
|
/* There are no resumed children left in the program. */
|
||||||
TARGET_WAITKIND_NO_RESUMED,
|
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. */
|
/* The thread was created. */
|
||||||
TARGET_WAITKIND_THREAD_CREATED,
|
TARGET_WAITKIND_THREAD_CREATED,
|
||||||
|
|
||||||
@@ -102,6 +109,17 @@ enum target_waitkind
|
|||||||
TARGET_WAITKIND_THREAD_EXITED,
|
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. */
|
/* Return KIND as a string. */
|
||||||
|
|
||||||
static inline const char *
|
static inline const char *
|
||||||
@@ -125,6 +143,8 @@ DIAGNOSTIC_ERROR_SWITCH
|
|||||||
return "FORKED";
|
return "FORKED";
|
||||||
case TARGET_WAITKIND_VFORKED:
|
case TARGET_WAITKIND_VFORKED:
|
||||||
return "VFORKED";
|
return "VFORKED";
|
||||||
|
case TARGET_WAITKIND_THREAD_CLONED:
|
||||||
|
return "THREAD_CLONED";
|
||||||
case TARGET_WAITKIND_EXECD:
|
case TARGET_WAITKIND_EXECD:
|
||||||
return "EXECD";
|
return "EXECD";
|
||||||
case TARGET_WAITKIND_VFORK_DONE:
|
case TARGET_WAITKIND_VFORK_DONE:
|
||||||
@@ -325,6 +345,14 @@ struct target_waitstatus
|
|||||||
return *this;
|
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 ()
|
target_waitstatus &set_thread_created ()
|
||||||
{
|
{
|
||||||
this->reset ();
|
this->reset ();
|
||||||
@@ -369,8 +397,7 @@ struct target_waitstatus
|
|||||||
|
|
||||||
ptid_t child_ptid () const
|
ptid_t child_ptid () const
|
||||||
{
|
{
|
||||||
gdb_assert (m_kind == TARGET_WAITKIND_FORKED
|
gdb_assert (is_new_child_status (m_kind));
|
||||||
|| m_kind == TARGET_WAITKIND_VFORKED);
|
|
||||||
return m_value.child_ptid;
|
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 } {
|
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"]
|
set syscall_insn_next_addr_found [get_hexadecimal_valueof "\$pc" "0"]
|
||||||
|
|
||||||
# After the 'stepi' we expect thread 1 to still be selected.
|
# 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"
|
set curr_thread "unknown"
|
||||||
gdb_test_multiple "info threads" "" {
|
gdb_test_multiple "thread" "" {
|
||||||
-re "Id\\s+Target Id\\s+Frame\\s*\r\n" {
|
-re -wrap "Current thread is (\\d+) .*" {
|
||||||
exp_continue
|
|
||||||
}
|
|
||||||
-re "^\\* (\\d+)\\s+\[^\r\n\]+\r\n" {
|
|
||||||
set curr_thread $expect_out(1,string)
|
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 \
|
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.*"
|
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.*" \
|
gdb_test "continue" "Continuing\\..*Breakpoint \[0-9\]+, marker \\(\\) at.*" \
|
||||||
"continue to marker ($syscall)"
|
"continue to marker ($syscall)"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,19 +233,11 @@ foreach_mi_ui_mode mode {
|
|||||||
exp_continue
|
exp_continue
|
||||||
}
|
}
|
||||||
|
|
||||||
# The output has arrived! Check how we did. There are other bugs
|
# The output has arrived! Check how we did.
|
||||||
# that come into play here which change what output we'll see.
|
gdb_assert { $saw_mi_thread_exited && $saw_mi_bp_deleted \
|
||||||
if { $saw_mi_thread_exited && $saw_mi_bp_deleted \
|
|
||||||
&& $saw_cli_thread_exited \
|
&& $saw_cli_thread_exited \
|
||||||
&& $saw_cli_bp_deleted } {
|
&& $saw_cli_bp_deleted } \
|
||||||
kpass "gdb/30129" $gdb_test_name
|
$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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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_breakpoint foo
|
||||||
gdb_test "continue" "Breakpoint $decimal, foo .*" \
|
gdb_test "continue" "Breakpoint $decimal, foo .*" \
|
||||||
"continue to 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} {
|
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
|
exp_continue
|
||||||
}
|
}
|
||||||
|
|
||||||
# When PR gdb/30129 is fixed then this can all be collapsed down
|
gdb_assert { $saw_thread_exited && $saw_bp_deleted } $gdb_test_name
|
||||||
# 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -21,38 +21,56 @@
|
|||||||
|
|
||||||
#include <asm/unistd.h>
|
#include <asm/unistd.h>
|
||||||
|
|
||||||
/* int my_execve (const char *file, char *argv[], char *envp[]); */
|
/* The SYSCALL macro below current supports calling syscalls with up
|
||||||
|
to 3 arguments, and, assumes the syscall never returns, like exec
|
||||||
.global my_execve
|
and exit. If you need to call syscalls with more arguments or you
|
||||||
my_execve:
|
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__)
|
#if defined(__x86_64__)
|
||||||
|
|
||||||
mov $__NR_execve, %rax
|
#define SYSCALL(NAME, NR) \
|
||||||
/* rdi, rsi and rdx already contain the right arguments. */
|
.global NAME ;\
|
||||||
my_execve_syscall:
|
NAME: ;\
|
||||||
syscall
|
mov $NR, %rax ;\
|
||||||
ret
|
/* rdi, rsi and rdx already contain the right arguments. */ \
|
||||||
|
NAME ## _syscall: ;\
|
||||||
|
syscall ;\
|
||||||
|
ret ;
|
||||||
|
|
||||||
#elif defined(__i386__)
|
#elif defined(__i386__)
|
||||||
|
|
||||||
mov $__NR_execve, %eax
|
#define SYSCALL(NAME, NR) \
|
||||||
mov 4(%esp), %ebx
|
.global NAME ;\
|
||||||
mov 8(%esp), %ecx
|
NAME: ;\
|
||||||
mov 12(%esp), %edx
|
mov $NR, %eax ;\
|
||||||
my_execve_syscall:
|
mov 4(%esp), %ebx ;\
|
||||||
int $0x80
|
mov 8(%esp), %ecx ;\
|
||||||
|
mov 12(%esp), %edx ;\
|
||||||
|
NAME ## _syscall: ;\
|
||||||
|
int $0x80 ;\
|
||||||
ret
|
ret
|
||||||
|
|
||||||
#elif defined(__aarch64__)
|
#elif defined(__aarch64__)
|
||||||
|
|
||||||
mov x8, #__NR_execve
|
#define SYSCALL(NAME, NR) \
|
||||||
/* x0, x1 and x2 already contain the right arguments. */
|
.global NAME ;\
|
||||||
my_execve_syscall:
|
NAME: ;\
|
||||||
|
mov x8, NR ;\
|
||||||
|
/* x0, x1 and x2 already contain the right arguments. */ \
|
||||||
|
NAME ## _syscall: ;\
|
||||||
svc #0
|
svc #0
|
||||||
|
|
||||||
#else
|
#else
|
||||||
# error "Unsupported architecture"
|
# error "Unsupported architecture"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
SYSCALL (my_execve, __NR_execve)
|
||||||
|
|
||||||
|
/* void my_exit (int code); */
|
||||||
|
|
||||||
|
SYSCALL (my_exit, __NR_exit)
|
||||||
|
|
||||||
.section .note.GNU-stack,"",@progbits
|
.section .note.GNU-stack,"",@progbits
|
||||||
|
|||||||
@@ -22,4 +22,9 @@
|
|||||||
|
|
||||||
int my_execve (const char *file, char *argv[], char *envp[]);
|
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 */
|
#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. */
|
/* See gdbthread.h. */
|
||||||
|
|
||||||
void
|
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. */
|
/* Dead threads don't need to step-over. Remove from chain. */
|
||||||
if (thread_is_in_step_over_chain (tp))
|
if (thread_is_in_step_over_chain (tp))
|
||||||
@@ -212,7 +213,22 @@ set_thread_exited (thread_info *tp, bool silent)
|
|||||||
if (proc_target != nullptr)
|
if (proc_target != nullptr)
|
||||||
proc_target->maybe_remove_resumed_with_pending_wait_status (tp);
|
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. */
|
/* Tag it as exited. */
|
||||||
tp->state = THREAD_EXITED;
|
tp->state = THREAD_EXITED;
|
||||||
@@ -236,7 +252,7 @@ init_thread_list (void)
|
|||||||
highest_thread_num = 0;
|
highest_thread_num = 0;
|
||||||
|
|
||||||
for (inferior *inf : all_inferiors ())
|
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
|
/* 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. */
|
/* 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
|
int
|
||||||
thread_is_in_step_over_chain (struct thread_info *tp)
|
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);
|
global_thread_step_over_list.erase (it);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Delete the thread referenced by THR. If SILENT, don't notify
|
/* Helper for the different delete_thread variants. */
|
||||||
the observer of this exit.
|
|
||||||
|
|
||||||
THR must not be NULL or a failed assertion will be raised. */
|
|
||||||
|
|
||||||
static void
|
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);
|
gdb_assert (thr != nullptr);
|
||||||
|
|
||||||
threads_debug_printf ("deleting thread %s, silent = %d",
|
threads_debug_printf ("deleting thread %s, exit_code = %s, silent = %d",
|
||||||
thr->ptid.to_string ().c_str (), silent);
|
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 ())
|
if (!thr->deletable ())
|
||||||
{
|
{
|
||||||
@@ -480,16 +516,25 @@ delete_thread_1 (thread_info *thr, bool silent)
|
|||||||
|
|
||||||
/* See gdbthread.h. */
|
/* 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
|
void
|
||||||
delete_thread (thread_info *thread)
|
delete_thread (thread_info *thread)
|
||||||
{
|
{
|
||||||
delete_thread_1 (thread, false /* not silent */);
|
delete_thread_1 (thread, {}, false /* not silent */);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
delete_thread_silent (thread_info *thread)
|
delete_thread_silent (thread_info *thread)
|
||||||
{
|
{
|
||||||
delete_thread_1 (thread, true /* silent */);
|
delete_thread_1 (thread, {}, true /* not silent */);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct thread_info *
|
struct thread_info *
|
||||||
|
|||||||
@@ -612,21 +612,13 @@ windows_nat_target::delete_thread (ptid_t ptid, DWORD exit_code,
|
|||||||
|
|
||||||
id = ptid.lwp ();
|
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
|
was created, and thus, unless in verbose mode, we should be
|
||||||
symmetrical, and avoid that notification for the main thread
|
symmetrical, and avoid that notification for the main thread
|
||||||
here as well. */
|
here as well. */
|
||||||
|
bool silent = (main_thread_p && !info_verbose);
|
||||||
if (info_verbose)
|
thread_info *todel = find_thread_ptid (this, ptid);
|
||||||
gdb_printf ("[Deleting %s]\n", target_pid_to_str (ptid).c_str ());
|
delete_thread_with_exit_code (todel, exit_code, silent);
|
||||||
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));
|
|
||||||
|
|
||||||
auto iter = std::find_if (windows_process.thread_list.begin (),
|
auto iter = std::find_if (windows_process.thread_list.begin (),
|
||||||
windows_process.thread_list.end (),
|
windows_process.thread_list.end (),
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ struct thread_info
|
|||||||
|
|
||||||
/* Branch trace target information for this thread. */
|
/* Branch trace target information for this thread. */
|
||||||
struct btrace_target_info *btrace = nullptr;
|
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;
|
extern std::list<thread_info *> all_threads;
|
||||||
|
|||||||
@@ -144,6 +144,18 @@ is_leader (thread_info *thread)
|
|||||||
return ptid.pid () == ptid.lwp ();
|
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. */
|
/* LWP accessors. */
|
||||||
|
|
||||||
/* See nat/linux-nat.h. */
|
/* See nat/linux-nat.h. */
|
||||||
@@ -267,7 +279,8 @@ int using_threads = 1;
|
|||||||
static int stabilizing_threads;
|
static int stabilizing_threads;
|
||||||
|
|
||||||
static void unsuspend_all_lwps (struct lwp_info *except);
|
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 lwp_is_marked_dead (struct lwp_info *lwp);
|
||||||
static int kill_lwp (unsigned long lwpid, int signo);
|
static int kill_lwp (unsigned long lwpid, int signo);
|
||||||
static void enqueue_pending_signal (struct lwp_info *lwp, int signal, siginfo_t *info);
|
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;
|
struct lwp_info *event_lwp = *orig_event_lwp;
|
||||||
int event = linux_ptrace_get_extended_event (wstat);
|
int event = linux_ptrace_get_extended_event (wstat);
|
||||||
struct thread_info *event_thr = get_lwp_thread (event_lwp);
|
struct thread_info *event_thr = get_lwp_thread (event_lwp);
|
||||||
struct lwp_info *new_lwp;
|
|
||||||
|
|
||||||
gdb_assert (event_lwp->waitstatus.kind () == TARGET_WAITKIND_IGNORE);
|
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)
|
if ((event == PTRACE_EVENT_FORK) || (event == PTRACE_EVENT_VFORK)
|
||||||
|| (event == PTRACE_EVENT_CLONE))
|
|| (event == PTRACE_EVENT_CLONE))
|
||||||
{
|
{
|
||||||
ptid_t ptid;
|
|
||||||
unsigned long new_pid;
|
unsigned long new_pid;
|
||||||
int ret, status;
|
int ret, status;
|
||||||
|
|
||||||
@@ -527,35 +538,29 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
|
|||||||
warning ("wait returned unexpected status 0x%x", status);
|
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;
|
debug_printf ("HEW: Got %s event from LWP %ld, new child is %ld\n",
|
||||||
struct process_info *child_proc;
|
(event == PTRACE_EVENT_FORK ? "fork"
|
||||||
struct lwp_info *child_lwp;
|
: event == PTRACE_EVENT_VFORK ? "vfork"
|
||||||
struct thread_info *child_thr;
|
: event == PTRACE_EVENT_CLONE ? "clone"
|
||||||
|
: "???"),
|
||||||
ptid = ptid_t (new_pid, new_pid);
|
|
||||||
|
|
||||||
threads_debug_printf ("Got fork event from LWP %ld, "
|
|
||||||
"new child is %d",
|
|
||||||
ptid_of (event_thr).lwp (),
|
ptid_of (event_thr).lwp (),
|
||||||
ptid.pid ());
|
new_pid);
|
||||||
|
}
|
||||||
|
|
||||||
/* Add the new process to the tables and clone the breakpoint
|
ptid_t child_ptid = (event != PTRACE_EVENT_CLONE
|
||||||
lists of the parent. We need to do this even if the new process
|
? ptid_t (new_pid, new_pid)
|
||||||
will be detached, since we will need the process object and the
|
: ptid_t (ptid_of (event_thr).pid (), new_pid));
|
||||||
breakpoints to remove any breakpoints from memory when we
|
|
||||||
detach, and the client side will access registers. */
|
lwp_info *child_lwp = add_lwp (child_ptid);
|
||||||
child_proc = add_linux_process (new_pid, 0);
|
|
||||||
gdb_assert (child_proc != NULL);
|
|
||||||
child_lwp = add_lwp (ptid);
|
|
||||||
gdb_assert (child_lwp != NULL);
|
gdb_assert (child_lwp != NULL);
|
||||||
child_lwp->stopped = 1;
|
child_lwp->stopped = 1;
|
||||||
|
if (event != PTRACE_EVENT_CLONE)
|
||||||
child_lwp->must_set_ptrace_flags = 1;
|
child_lwp->must_set_ptrace_flags = 1;
|
||||||
child_lwp->status_pending_p = 0;
|
child_lwp->status_pending_p = 0;
|
||||||
child_thr = get_lwp_thread (child_lwp);
|
|
||||||
child_thr->last_resume_kind = resume_stop;
|
thread_info *child_thr = get_lwp_thread (child_lwp);
|
||||||
child_thr->last_status.set_stopped (GDB_SIGNAL_0);
|
|
||||||
|
|
||||||
/* If we're suspending all threads, leave this one suspended
|
/* If we're suspending all threads, leave this one suspended
|
||||||
too. If the fork/clone parent is stepping over a breakpoint,
|
too. If the fork/clone parent is stepping over a breakpoint,
|
||||||
@@ -568,9 +573,6 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
|
|||||||
child_lwp->suspended = 1;
|
child_lwp->suspended = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
parent_proc = get_thread_process (event_thr);
|
|
||||||
child_proc->attached = parent_proc->attached;
|
|
||||||
|
|
||||||
if (event_lwp->bp_reinsert != 0
|
if (event_lwp->bp_reinsert != 0
|
||||||
&& supports_software_single_step ()
|
&& supports_software_single_step ()
|
||||||
&& event == PTRACE_EVENT_VFORK)
|
&& event == PTRACE_EVENT_VFORK)
|
||||||
@@ -582,6 +584,19 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
|
|||||||
uninsert_single_step_breakpoints (event_thr);
|
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. */
|
||||||
|
process_info *child_proc = add_linux_process (new_pid, 0);
|
||||||
|
gdb_assert (child_proc != NULL);
|
||||||
|
|
||||||
|
process_info *parent_proc = get_thread_process (event_thr);
|
||||||
|
child_proc->attached = parent_proc->attached;
|
||||||
|
|
||||||
clone_all_breakpoints (child_thr, event_thr);
|
clone_all_breakpoints (child_thr, event_thr);
|
||||||
|
|
||||||
target_desc_up tdesc = allocate_target_description ();
|
target_desc_up tdesc = allocate_target_description ();
|
||||||
@@ -590,23 +605,31 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
|
|||||||
|
|
||||||
/* Clone arch-specific process data. */
|
/* Clone arch-specific process data. */
|
||||||
low_new_fork (parent_proc, child_proc);
|
low_new_fork (parent_proc, child_proc);
|
||||||
|
}
|
||||||
|
|
||||||
/* Save fork info in the parent thread. */
|
/* Save fork/clone info in the parent thread. */
|
||||||
if (event == PTRACE_EVENT_FORK)
|
if (event == PTRACE_EVENT_FORK)
|
||||||
event_lwp->waitstatus.set_forked (ptid);
|
event_lwp->waitstatus.set_forked (child_ptid);
|
||||||
else if (event == PTRACE_EVENT_VFORK)
|
else if (event == PTRACE_EVENT_VFORK)
|
||||||
event_lwp->waitstatus.set_vforked (ptid);
|
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
|
/* The status_pending field contains bits denoting the
|
||||||
extended event, so when the pending event is handled,
|
extended event, so when the pending event is handled, the
|
||||||
the handler will look at lwp->waitstatus. */
|
handler will look at lwp->waitstatus. */
|
||||||
event_lwp->status_pending_p = 1;
|
event_lwp->status_pending_p = 1;
|
||||||
event_lwp->status_pending = wstat;
|
event_lwp->status_pending = wstat;
|
||||||
|
|
||||||
/* Link the threads until the parent event is passed on to
|
/* Link the threads until the parent's event is passed on to
|
||||||
higher layers. */
|
GDB. */
|
||||||
event_lwp->fork_relative = child_lwp;
|
event_lwp->fork_relative = child_lwp;
|
||||||
child_lwp->fork_relative = event_lwp;
|
child_lwp->fork_relative = event_lwp;
|
||||||
|
}
|
||||||
|
|
||||||
/* If the parent thread is doing step-over with single-step
|
/* If the parent thread is doing step-over with single-step
|
||||||
breakpoints, the list of single-step breakpoints are cloned
|
breakpoints, the list of single-step breakpoints are cloned
|
||||||
@@ -625,54 +648,55 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
|
|||||||
gdb_assert (!has_single_step_breakpoints (child_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);
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
/* Normally we will get the pending SIGSTOP. But in some cases
|
/* Normally we will get the pending SIGSTOP. But in some cases
|
||||||
we might get another signal delivered to the group first.
|
we might get another signal delivered to the group first.
|
||||||
If we do get another signal, be sure not to lose it. */
|
If we do get another signal, be sure not to lose it. */
|
||||||
if (WSTOPSIG (status) != SIGSTOP)
|
if (WSTOPSIG (status) != SIGSTOP)
|
||||||
{
|
{
|
||||||
new_lwp->stop_expected = 1;
|
child_lwp->stop_expected = 1;
|
||||||
new_lwp->status_pending_p = 1;
|
child_lwp->status_pending_p = 1;
|
||||||
new_lwp->status_pending = status;
|
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 ();
|
child_lwp->waitstatus.set_thread_created ();
|
||||||
new_lwp->status_pending_p = 1;
|
child_lwp->status_pending_p = 1;
|
||||||
new_lwp->status_pending = status;
|
child_lwp->status_pending = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event == PTRACE_EVENT_CLONE)
|
||||||
|
{
|
||||||
#ifdef USE_THREAD_DB
|
#ifdef USE_THREAD_DB
|
||||||
thread_db_notice_clone (event_thr, ptid);
|
thread_db_notice_clone (event_thr, child_ptid);
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/* Don't report the event. */
|
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;
|
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)
|
else if (event == PTRACE_EVENT_VFORK_DONE)
|
||||||
{
|
{
|
||||||
event_lwp->waitstatus.set_vfork_done ();
|
event_lwp->waitstatus.set_vfork_done ();
|
||||||
@@ -1777,10 +1801,12 @@ iterate_over_lwps (ptid_t filter,
|
|||||||
return get_thread_lwp (thread);
|
return get_thread_lwp (thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
linux_process_target::check_zombie_leaders ()
|
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);
|
pid_t leader_pid = pid_of (proc);
|
||||||
lwp_info *leader_lp = find_lwp_pid (ptid_t (leader_pid));
|
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), "
|
"(it exited, or another thread execd), "
|
||||||
"deleting it.",
|
"deleting it.",
|
||||||
leader_pid);
|
leader_pid);
|
||||||
|
|
||||||
|
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);
|
delete_lwp (leader_lp);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return new_pending_event;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Callback for `find_thread'. Returns the first LWP that is not
|
/* Callback for `find_thread'. Returns the first LWP that is not
|
||||||
@@ -2219,7 +2255,6 @@ linux_low_ptrace_options (int attached)
|
|||||||
void
|
void
|
||||||
linux_process_target::filter_event (int lwpid, int wstat)
|
linux_process_target::filter_event (int lwpid, int wstat)
|
||||||
{
|
{
|
||||||
client_state &cs = get_client_state ();
|
|
||||||
struct lwp_info *child;
|
struct lwp_info *child;
|
||||||
struct thread_info *thread;
|
struct thread_info *thread;
|
||||||
int have_stop_pc = 0;
|
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
|
/* If this is not the leader LWP, then the exit signal was not
|
||||||
the end of the debugged application and should be ignored,
|
the end of the debugged application and should be ignored,
|
||||||
unless GDB wants to hear about thread exits. */
|
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
|
/* Since events are serialized to GDB core, and we can't
|
||||||
report this one right now. Leave the status pending for
|
report this one right now. Leave the status pending for
|
||||||
the next time we're able to report it. */
|
the next time we're able to report it. */
|
||||||
mark_lwp_dead (child, wstat);
|
mark_lwp_dead (child, wstat, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
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
|
/* Check for zombie thread group leaders. Those can't be reaped
|
||||||
until all other threads in the thread group are. */
|
until all other threads in the thread group are. */
|
||||||
check_zombie_leaders ();
|
if (check_zombie_leaders ())
|
||||||
|
goto retry;
|
||||||
|
|
||||||
auto not_stopped = [&] (thread_info *thread)
|
auto not_stopped = [&] (thread_info *thread)
|
||||||
{
|
{
|
||||||
@@ -2868,13 +2904,31 @@ ptid_t
|
|||||||
linux_process_target::filter_exit_event (lwp_info *event_child,
|
linux_process_target::filter_exit_event (lwp_info *event_child,
|
||||||
target_waitstatus *ourstatus)
|
target_waitstatus *ourstatus)
|
||||||
{
|
{
|
||||||
client_state &cs = get_client_state ();
|
|
||||||
struct thread_info *thread = get_lwp_thread (event_child);
|
struct thread_info *thread = get_lwp_thread (event_child);
|
||||||
ptid_t ptid = ptid_of (thread);
|
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 (!is_leader (thread))
|
||||||
{
|
{
|
||||||
if (cs.report_thread_events)
|
if (report_exit_events_for (thread))
|
||||||
ourstatus->set_thread_exited (0);
|
ourstatus->set_thread_exited (0);
|
||||||
else
|
else
|
||||||
ourstatus->set_ignore ();
|
ourstatus->set_ignore ();
|
||||||
@@ -2934,7 +2988,6 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
|||||||
int report_to_gdb;
|
int report_to_gdb;
|
||||||
int trace_event;
|
int trace_event;
|
||||||
int in_step_range;
|
int in_step_range;
|
||||||
int any_resumed;
|
|
||||||
|
|
||||||
threads_debug_printf ("[%s]", target_pid_to_str (ptid).c_str ());
|
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;
|
in_step_range = 0;
|
||||||
ourstatus->set_ignore ();
|
ourstatus->set_ignore ();
|
||||||
|
|
||||||
auto status_pending_p_any = [&] (thread_info *thread)
|
bool was_any_resumed = any_resumed ();
|
||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
if (step_over_bkpt == null_ptid)
|
if (step_over_bkpt == null_ptid)
|
||||||
pid = wait_for_event (ptid, &w, options);
|
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);
|
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);
|
gdb_assert (target_options & TARGET_WNOHANG);
|
||||||
|
|
||||||
@@ -3000,6 +3037,19 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
|||||||
{
|
{
|
||||||
if (WIFEXITED (w))
|
if (WIFEXITED (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));
|
ourstatus->set_exited (WEXITSTATUS (w));
|
||||||
|
|
||||||
threads_debug_printf
|
threads_debug_printf
|
||||||
@@ -3017,10 +3067,7 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
|||||||
WTERMSIG (w));
|
WTERMSIG (w));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ourstatus->kind () == TARGET_WAITKIND_EXITED)
|
|
||||||
return filter_exit_event (event_child, ourstatus);
|
return filter_exit_event (event_child, ourstatus);
|
||||||
|
|
||||||
return ptid_of (current_thread);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If step-over executes a breakpoint instruction, in the case of a
|
/* 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. */
|
/* Break the unreported fork relationship chain. */
|
||||||
if (event_child->waitstatus.kind () == TARGET_WAITKIND_FORKED
|
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->fork_relative = NULL;
|
||||||
event_child->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 (),
|
target_pid_to_str (ptid_of (current_thread)).c_str (),
|
||||||
ourstatus->to_string ().c_str ());
|
ourstatus->to_string ().c_str ());
|
||||||
|
|
||||||
if (ourstatus->kind () == TARGET_WAITKIND_EXITED)
|
|
||||||
return filter_exit_event (event_child, ourstatus);
|
return filter_exit_event (event_child, ourstatus);
|
||||||
|
|
||||||
return ptid_of (current_thread);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get rid of any pending event in the pipe. */
|
/* 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);
|
event_ptid = wait_1 (ptid, ourstatus, target_options);
|
||||||
}
|
}
|
||||||
while ((target_options & TARGET_WNOHANG) == 0
|
while ((target_options & TARGET_WNOHANG) == 0
|
||||||
&& event_ptid == null_ptid
|
|
||||||
&& ourstatus->kind () == TARGET_WAITKIND_IGNORE);
|
&& ourstatus->kind () == TARGET_WAITKIND_IGNORE);
|
||||||
|
|
||||||
/* If at least one stop was reported, there may be more. A single
|
/* 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);
|
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
|
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. */
|
/* Store the exit status for later. */
|
||||||
lwp->status_pending_p = 1;
|
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
|
/* Store in waitstatus as well, as there's nothing else to process
|
||||||
for this event. */
|
for this event. */
|
||||||
if (WIFEXITED (wstat))
|
if (WIFEXITED (wstat))
|
||||||
|
{
|
||||||
|
if (thread_event)
|
||||||
|
lwp->waitstatus.set_thread_exited (WEXITSTATUS (wstat));
|
||||||
|
else
|
||||||
lwp->waitstatus.set_exited (WEXITSTATUS (wstat));
|
lwp->waitstatus.set_exited (WEXITSTATUS (wstat));
|
||||||
|
}
|
||||||
else if (WIFSIGNALED (wstat))
|
else if (WIFSIGNALED (wstat))
|
||||||
|
{
|
||||||
|
gdb_assert (!thread_event);
|
||||||
lwp->waitstatus.set_signalled (gdb_signal_from_host (WTERMSIG (wstat)));
|
lwp->waitstatus.set_signalled (gdb_signal_from_host (WTERMSIG (wstat)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
gdb_assert_not_reached ("unknown status kind");
|
||||||
|
|
||||||
/* Prevent trying to stop it. */
|
/* Prevent trying to stop it. */
|
||||||
lwp->stopped = 1;
|
lwp->stopped = 1;
|
||||||
@@ -4263,15 +4324,14 @@ linux_set_resume_request (thread_info *thread, thread_resume *resume, size_t n)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Don't let wildcard resumes resume fork children that GDB
|
/* Don't let wildcard resumes resume fork/vfork/clone
|
||||||
does not yet know are new fork children. */
|
children that GDB does not yet know are new children. */
|
||||||
if (lwp->fork_relative != NULL)
|
if (lwp->fork_relative != NULL)
|
||||||
{
|
{
|
||||||
struct lwp_info *rel = lwp->fork_relative;
|
struct lwp_info *rel = lwp->fork_relative;
|
||||||
|
|
||||||
if (rel->status_pending_p
|
if (rel->status_pending_p
|
||||||
&& (rel->waitstatus.kind () == TARGET_WAITKIND_FORKED
|
&& is_new_child_status (rel->waitstatus.kind ()))
|
||||||
|| rel->waitstatus.kind () == TARGET_WAITKIND_VFORKED))
|
|
||||||
{
|
{
|
||||||
threads_debug_printf
|
threads_debug_printf
|
||||||
("not resuming LWP %ld: has queued stop reply",
|
("not resuming LWP %ld: has queued stop reply",
|
||||||
@@ -5893,6 +5953,14 @@ linux_process_target::supports_vfork_events ()
|
|||||||
return true;
|
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. */
|
/* Check if exec events are supported. */
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@@ -6135,6 +6203,32 @@ linux_process_target::thread_stopped (thread_info *thread)
|
|||||||
return get_thread_lwp (thread)->stopped;
|
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. */
|
/* This exposes stop-all-threads functionality to other modules. */
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -6920,9 +7014,10 @@ linux_process_target::thread_pending_parent (thread_info *thread)
|
|||||||
}
|
}
|
||||||
|
|
||||||
thread_info *
|
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)
|
if (child == nullptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|||||||
@@ -234,6 +234,8 @@ public:
|
|||||||
|
|
||||||
bool supports_vfork_events () override;
|
bool supports_vfork_events () override;
|
||||||
|
|
||||||
|
gdb_thread_options supported_thread_options () override;
|
||||||
|
|
||||||
bool supports_exec_events () override;
|
bool supports_exec_events () override;
|
||||||
|
|
||||||
void handle_new_gdb_connection () override;
|
void handle_new_gdb_connection () override;
|
||||||
@@ -257,6 +259,8 @@ public:
|
|||||||
|
|
||||||
bool thread_stopped (thread_info *thread) override;
|
bool thread_stopped (thread_info *thread) override;
|
||||||
|
|
||||||
|
bool any_resumed () override;
|
||||||
|
|
||||||
void pause_all (bool freeze) override;
|
void pause_all (bool freeze) override;
|
||||||
|
|
||||||
void unpause_all (bool unfreeze) override;
|
void unpause_all (bool unfreeze) override;
|
||||||
@@ -313,7 +317,8 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
thread_info *thread_pending_parent (thread_info *thread) override;
|
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;
|
bool supports_catch_syscall () override;
|
||||||
|
|
||||||
@@ -569,13 +574,15 @@ private: /* Back to private. */
|
|||||||
|
|
||||||
/* Detect zombie thread group leaders, and "exit" them. We can't
|
/* Detect zombie thread group leaders, and "exit" them. We can't
|
||||||
reap their exits until all other threads in the group have
|
reap their exits until all other threads in the group have
|
||||||
exited. */
|
exited. Returns true if we left any new event pending, false
|
||||||
void check_zombie_leaders ();
|
otherwise. */
|
||||||
|
bool check_zombie_leaders ();
|
||||||
|
|
||||||
/* Convenience function that is called when the kernel reports an exit
|
/* Convenience function that is called when we're about to return an
|
||||||
event. This decides whether to report the event to GDB as a
|
event to the core. If the event is an exit or signalled event,
|
||||||
process exit event, a thread exit event, or to suppress the
|
then this decides whether to report it as process-wide event, as
|
||||||
event. */
|
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,
|
ptid_t filter_exit_event (lwp_info *event_child,
|
||||||
target_waitstatus *ourstatus);
|
target_waitstatus *ourstatus);
|
||||||
|
|
||||||
@@ -732,8 +739,8 @@ struct pending_signal
|
|||||||
|
|
||||||
struct lwp_info
|
struct lwp_info
|
||||||
{
|
{
|
||||||
/* If this LWP is a fork child that wasn't reported to GDB yet, return
|
/* If this LWP is a fork/vfork/clone child that wasn't reported to
|
||||||
its parent, else nullptr. */
|
GDB yet, return its parent, else nullptr. */
|
||||||
lwp_info *pending_parent () const
|
lwp_info *pending_parent () const
|
||||||
{
|
{
|
||||||
if (this->fork_relative == nullptr)
|
if (this->fork_relative == nullptr)
|
||||||
@@ -741,10 +748,10 @@ struct lwp_info
|
|||||||
|
|
||||||
gdb_assert (this->fork_relative->fork_relative == this);
|
gdb_assert (this->fork_relative->fork_relative == this);
|
||||||
|
|
||||||
/* In a fork parent/child relationship, the parent has a status pending and
|
/* In a parent/child relationship, the parent has a status pending
|
||||||
the child does not, and a thread can only be in one such relationship
|
and the child does not, and a thread can only be in one such
|
||||||
at most. So we can recognize who is the parent based on which one has
|
relationship at most. So we can recognize who is the parent
|
||||||
a pending status. */
|
based on which one has a pending status. */
|
||||||
gdb_assert (!!this->status_pending_p
|
gdb_assert (!!this->status_pending_p
|
||||||
!= !!this->fork_relative->status_pending_p);
|
!= !!this->fork_relative->status_pending_p);
|
||||||
|
|
||||||
@@ -754,24 +761,25 @@ struct lwp_info
|
|||||||
const target_waitstatus &ws
|
const target_waitstatus &ws
|
||||||
= this->fork_relative->waitstatus;
|
= this->fork_relative->waitstatus;
|
||||||
gdb_assert (ws.kind () == TARGET_WAITKIND_FORKED
|
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;
|
return this->fork_relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If this LWP is the parent of a fork child we haven't reported to GDB yet,
|
/* If this LWP is the parent of a fork/vfork/clone child we haven't
|
||||||
return that child, else nullptr. */
|
reported to GDB yet, return that child, else nullptr. */
|
||||||
lwp_info *pending_child () const
|
lwp_info *pending_child (target_waitkind *kind) const
|
||||||
{
|
{
|
||||||
if (this->fork_relative == nullptr)
|
if (this->fork_relative == nullptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
gdb_assert (this->fork_relative->fork_relative == this);
|
gdb_assert (this->fork_relative->fork_relative == this);
|
||||||
|
|
||||||
/* In a fork parent/child relationship, the parent has a status pending and
|
/* In a parent/child relationship, the parent has a status pending
|
||||||
the child does not, and a thread can only be in one such relationship
|
and the child does not, and a thread can only be in one such
|
||||||
at most. So we can recognize who is the parent based on which one has
|
relationship at most. So we can recognize who is the parent
|
||||||
a pending status. */
|
based on which one has a pending status. */
|
||||||
gdb_assert (!!this->status_pending_p
|
gdb_assert (!!this->status_pending_p
|
||||||
!= !!this->fork_relative->status_pending_p);
|
!= !!this->fork_relative->status_pending_p);
|
||||||
|
|
||||||
@@ -780,8 +788,10 @@ struct lwp_info
|
|||||||
|
|
||||||
const target_waitstatus &ws = this->waitstatus;
|
const target_waitstatus &ws = this->waitstatus;
|
||||||
gdb_assert (ws.kind () == TARGET_WAITKIND_FORKED
|
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;
|
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_FORKED:
|
||||||
case TARGET_WAITKIND_VFORKED:
|
case TARGET_WAITKIND_VFORKED:
|
||||||
case TARGET_WAITKIND_VFORK_DONE:
|
case TARGET_WAITKIND_VFORK_DONE:
|
||||||
|
case TARGET_WAITKIND_THREAD_CLONED:
|
||||||
case TARGET_WAITKIND_EXECD:
|
case TARGET_WAITKIND_EXECD:
|
||||||
case TARGET_WAITKIND_THREAD_CREATED:
|
case TARGET_WAITKIND_THREAD_CREATED:
|
||||||
case TARGET_WAITKIND_SYSCALL_ENTRY:
|
case TARGET_WAITKIND_SYSCALL_ENTRY:
|
||||||
@@ -1071,13 +1072,30 @@ prepare_resume_reply (char *buf, ptid_t ptid, const target_waitstatus &status)
|
|||||||
struct regcache *regcache;
|
struct regcache *regcache;
|
||||||
char *buf_start = buf;
|
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
|
|| (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;
|
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);
|
sprintf (buf, "T%02x%s:", signal, event);
|
||||||
buf += strlen (buf);
|
buf += strlen (buf);
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include "dll.h"
|
#include "dll.h"
|
||||||
#include "hostio.h"
|
#include "hostio.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
#include "gdbsupport/common-inferior.h"
|
#include "gdbsupport/common-inferior.h"
|
||||||
#include "gdbsupport/job-control.h"
|
#include "gdbsupport/job-control.h"
|
||||||
#include "gdbsupport/environ.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. */
|
/* Don't resume fork children that GDB does not know about yet. */
|
||||||
if ((vstop_event->status.kind () == TARGET_WAITKIND_FORKED
|
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))
|
&& vstop_event->status.child_ptid ().matches (filter_ptid))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -615,6 +617,17 @@ parse_store_memtags_request (char *request, CORE_ADDR *addr, size_t *len,
|
|||||||
return true;
|
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. */
|
/* Handle all of the extended 'Q' packets. */
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -896,6 +909,114 @@ handle_general_set (char *own_buf)
|
|||||||
return;
|
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:"))
|
if (startswith (own_buf, "QStartupWithShell:"))
|
||||||
{
|
{
|
||||||
const char *value = own_buf + strlen ("QStartupWithShell:");
|
const char *value = own_buf + strlen ("QStartupWithShell:");
|
||||||
@@ -1228,8 +1349,9 @@ handle_detach (char *own_buf)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Only threads that have a pending fork event. */
|
/* Only threads that have a pending fork event. */
|
||||||
thread_info *child = target_thread_pending_child (thread);
|
target_waitkind kind;
|
||||||
if (child == nullptr)
|
thread_info *child = target_thread_pending_child (thread, &kind);
|
||||||
|
if (child == nullptr || kind == TARGET_WAITKIND_THREAD_CLONED)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
process_info *fork_child_process = get_thread_process (child);
|
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;
|
gdb_byte *handle;
|
||||||
bool handle_status = target_thread_handle (ptid, &handle, &handle_len);
|
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
|
/* If this is a (v)fork/clone child (has a (v)fork/clone parent),
|
||||||
know about this process, and must not know about it until it gets the
|
GDB does not yet know about this thread, and must not know about
|
||||||
corresponding (v)fork event. Exclude this thread from the list. */
|
it until it gets the corresponding (v)fork/clone event. Exclude
|
||||||
|
this thread from the list. */
|
||||||
if (target_thread_pending_parent (thread) != nullptr)
|
if (target_thread_pending_parent (thread) != nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -2347,6 +2470,8 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
|
|||||||
cs.vCont_supported = 1;
|
cs.vCont_supported = 1;
|
||||||
else if (feature == "QThreadEvents+")
|
else if (feature == "QThreadEvents+")
|
||||||
;
|
;
|
||||||
|
else if (feature == "QThreadOptions+")
|
||||||
|
;
|
||||||
else if (feature == "no-resumed+")
|
else if (feature == "no-resumed+")
|
||||||
{
|
{
|
||||||
/* GDB supports and wants TARGET_WAITKIND_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+");
|
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, ";QThreadEvents+");
|
||||||
|
|
||||||
strcat (own_buf, ";no-resumed+");
|
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
|
if (cs.last_status.kind () != TARGET_WAITKIND_EXITED
|
||||||
&& cs.last_status.kind () != TARGET_WAITKIND_SIGNALLED
|
&& cs.last_status.kind () != TARGET_WAITKIND_SIGNALLED
|
||||||
|
&& cs.last_status.kind () != TARGET_WAITKIND_THREAD_EXITED
|
||||||
&& cs.last_status.kind () != TARGET_WAITKIND_NO_RESUMED)
|
&& cs.last_status.kind () != TARGET_WAITKIND_NO_RESUMED)
|
||||||
current_thread->last_status = cs.last_status;
|
current_thread->last_status = cs.last_status;
|
||||||
|
|
||||||
@@ -4590,7 +4724,17 @@ handle_target_event (int err, gdb_client_data client_data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
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.
|
/* Be sure to not change the selected thread behind GDB's back.
|
||||||
|
|||||||
@@ -532,6 +532,12 @@ process_stratum_target::supports_vfork_events ()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gdb_thread_options
|
||||||
|
process_stratum_target::supported_thread_options ()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
process_stratum_target::supports_exec_events ()
|
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");
|
gdb_assert_not_reached ("target op thread_stopped not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
process_stratum_target::any_resumed ()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
process_stratum_target::supports_get_tib_address ()
|
process_stratum_target::supports_get_tib_address ()
|
||||||
{
|
{
|
||||||
@@ -810,7 +822,8 @@ process_stratum_target::thread_pending_parent (thread_info *thread)
|
|||||||
}
|
}
|
||||||
|
|
||||||
thread_info *
|
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;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -276,6 +276,9 @@ public:
|
|||||||
/* Returns true if vfork events are supported. */
|
/* Returns true if vfork events are supported. */
|
||||||
virtual bool supports_vfork_events ();
|
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. */
|
/* Returns true if exec events are supported. */
|
||||||
virtual bool supports_exec_events ();
|
virtual bool supports_exec_events ();
|
||||||
|
|
||||||
@@ -316,6 +319,9 @@ public:
|
|||||||
/* Return true if THREAD is known to be stopped now. */
|
/* Return true if THREAD is known to be stopped now. */
|
||||||
virtual bool thread_stopped (thread_info *thread);
|
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. */
|
/* Return true if the get_tib_address op is supported. */
|
||||||
virtual bool supports_get_tib_address ();
|
virtual bool supports_get_tib_address ();
|
||||||
|
|
||||||
@@ -475,13 +481,14 @@ public:
|
|||||||
virtual bool thread_handle (ptid_t ptid, gdb_byte **handle,
|
virtual bool thread_handle (ptid_t ptid, gdb_byte **handle,
|
||||||
int *handle_len);
|
int *handle_len);
|
||||||
|
|
||||||
/* If THREAD is a fork child that was not reported to GDB, return its parent
|
/* If THREAD is a fork/vfork/clone child that was not reported to
|
||||||
else nullptr. */
|
GDB, return its parent else nullptr. */
|
||||||
virtual thread_info *thread_pending_parent (thread_info *thread);
|
virtual thread_info *thread_pending_parent (thread_info *thread);
|
||||||
|
|
||||||
/* If THREAD is the parent of a fork child that was not reported to GDB,
|
/* If THREAD is the parent of a fork/vfork/clone child that was not
|
||||||
return this child, else nullptr. */
|
reported to GDB, return this child, else nullptr. */
|
||||||
virtual thread_info *thread_pending_child (thread_info *thread);
|
virtual thread_info *thread_pending_child (thread_info *thread,
|
||||||
|
target_waitkind *kind);
|
||||||
|
|
||||||
/* Returns true if the target can software single step. */
|
/* Returns true if the target can software single step. */
|
||||||
virtual bool supports_software_single_step ();
|
virtual bool supports_software_single_step ();
|
||||||
@@ -531,6 +538,9 @@ int kill_inferior (process_info *proc);
|
|||||||
#define target_supports_vfork_events() \
|
#define target_supports_vfork_events() \
|
||||||
the_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() \
|
#define target_supports_exec_events() \
|
||||||
the_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() \
|
#define target_supports_software_single_step() \
|
||||||
the_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,
|
ptid_t mywait (ptid_t ptid, struct target_waitstatus *ourstatus,
|
||||||
target_wait_flags options, int connected_wait);
|
target_wait_flags options, int connected_wait);
|
||||||
|
|
||||||
@@ -694,9 +707,9 @@ target_thread_pending_parent (thread_info *thread)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline thread_info *
|
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
|
/* Read LEN bytes from MEMADDR in the buffer MYADDR. Return 0 if the read
|
||||||
|
|||||||
Reference in New Issue
Block a user