forked from Imagelibrary/binutils-gdb
Compare commits
37 Commits
gdb-16.2-r
...
users/palv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37dff3c3bb | ||
|
|
daf510bf56 | ||
|
|
657a94e242 | ||
|
|
1bbadb3300 | ||
|
|
15f070da4e | ||
|
|
cd204162fa | ||
|
|
9d40da2ccb | ||
|
|
e2e59def17 | ||
|
|
55144e8727 | ||
|
|
190583a1b6 | ||
|
|
658b3049fa | ||
|
|
947d6aa1d6 | ||
|
|
0bef4a9b24 | ||
|
|
d1286b7787 | ||
|
|
f505d61330 | ||
|
|
dc8b2cd333 | ||
|
|
da1fac1872 | ||
|
|
925d3dc8a1 | ||
|
|
f072ced747 | ||
|
|
996fa696e6 | ||
|
|
cd384f6e93 | ||
|
|
685ac58d0d | ||
|
|
72df7c538f | ||
|
|
883e02207f | ||
|
|
cbe8ae27b1 | ||
|
|
69ab09e83b | ||
|
|
9da4b393c5 | ||
|
|
74123a7008 | ||
|
|
7699b88887 | ||
|
|
a824020224 | ||
|
|
8dee763607 | ||
|
|
15b5cf5c2d | ||
|
|
e981a876ab | ||
|
|
43fac29628 | ||
|
|
8e958b8349 | ||
|
|
ddd4202830 | ||
|
|
d34abbc2d7 |
27
gdb/NEWS
27
gdb/NEWS
@@ -15,6 +15,13 @@
|
|||||||
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:
|
||||||
|
|
||||||
@@ -153,6 +160,10 @@ set style tui-current-position [on|off]
|
|||||||
Whether to style the source and assembly code highlighted by the
|
Whether to style the source and assembly code highlighted by the
|
||||||
TUI's current position indicator. The default is off.
|
TUI's current position indicator. The default is off.
|
||||||
|
|
||||||
|
set remote thread-options-packet
|
||||||
|
show remote thread-options-packet
|
||||||
|
Set/show the use of the thread options packet.
|
||||||
|
|
||||||
* Changed commands
|
* Changed commands
|
||||||
|
|
||||||
document user-defined
|
document user-defined
|
||||||
@@ -278,6 +289,22 @@ GNU/Linux/CSKY (gdbserver) csky*-*linux*
|
|||||||
|
|
||||||
GDB now supports floating-point on LoongArch GNU/Linux.
|
GDB now supports floating-point on LoongArch GNU/Linux.
|
||||||
|
|
||||||
|
* 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 12
|
*** Changes in GDB 12
|
||||||
|
|
||||||
* DBX mode is deprecated, and will be removed in GDB 13
|
* DBX mode is deprecated, and will be removed in GDB 13
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3237,7 +3237,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,10 +192,11 @@ 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;
|
||||||
|
|
||||||
if (target_stopped_by_watchpoint ())
|
if (target_stopped_by_watchpoint ())
|
||||||
@@ -210,7 +211,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 +254,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);
|
||||||
|
|||||||
@@ -7037,7 +7037,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.
|
||||||
@@ -24077,6 +24079,10 @@ 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.
|
||||||
@@ -42108,6 +42114,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
|
||||||
@@ -42146,9 +42163,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
|
||||||
@@ -42873,6 +42891,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
|
||||||
@@ -42889,6 +42912,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
|
||||||
@@ -43334,6 +43445,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{-}
|
||||||
@@ -43555,6 +43671,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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1826,10 +1826,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)",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1078,10 +1078,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. */
|
||||||
|
|||||||
@@ -4097,13 +4097,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
|
||||||
|
|||||||
@@ -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 *);
|
||||||
|
|||||||
@@ -522,7 +522,7 @@ inf_ptrace_target::files_info ()
|
|||||||
|
|
||||||
gdb_printf (_("\tUsing the running image of %s %s.\n"),
|
gdb_printf (_("\tUsing the running image of %s %s.\n"),
|
||||||
inf->attach_flag ? "attached" : "child",
|
inf->attach_flag ? "attached" : "child",
|
||||||
target_pid_to_str (inferior_ptid).c_str ());
|
target_pid_to_str (ptid_t (inf->pid)).c_str ());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
|
|||||||
84
gdb/infcmd.c
84
gdb/infcmd.c
@@ -1937,37 +1937,74 @@ finish_command (const char *arg, int from_tty)
|
|||||||
static void
|
static void
|
||||||
info_program_command (const char *args, int from_tty)
|
info_program_command (const char *args, int from_tty)
|
||||||
{
|
{
|
||||||
bpstat *bs;
|
scoped_restore_current_thread restore_thread;
|
||||||
int num, stat;
|
|
||||||
ptid_t ptid;
|
|
||||||
process_stratum_target *proc_target;
|
|
||||||
|
|
||||||
if (!target_has_execution ())
|
thread_info *tp;
|
||||||
{
|
|
||||||
gdb_printf (_("The program being debugged is not being run.\n"));
|
/* In non-stop, since every thread is controlled individually, we'll
|
||||||
return;
|
show execution info about the current thread. In all-stop, we'll
|
||||||
}
|
show execution info about the last stop. */
|
||||||
|
|
||||||
if (non_stop)
|
if (non_stop)
|
||||||
{
|
{
|
||||||
ptid = inferior_ptid;
|
if (!target_has_execution ())
|
||||||
proc_target = current_inferior ()->process_target ();
|
{
|
||||||
|
gdb_printf (_("The program being debugged is not being run.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inferior_ptid == null_ptid)
|
||||||
|
error (_("No selected thread."));
|
||||||
|
|
||||||
|
tp = inferior_thread ();
|
||||||
|
|
||||||
|
gdb_printf (_("Selected thread %s (%s).\n"),
|
||||||
|
print_thread_id (tp),
|
||||||
|
target_pid_to_str (tp->ptid).c_str ());
|
||||||
|
|
||||||
|
if (tp->state == THREAD_EXITED)
|
||||||
|
{
|
||||||
|
gdb_printf (_("Selected thread has exited.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (tp->state == THREAD_RUNNING)
|
||||||
|
{
|
||||||
|
gdb_printf (_("Selected thread is running.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
get_last_target_status (&proc_target, &ptid, nullptr);
|
{
|
||||||
|
tp = get_previous_thread ();
|
||||||
|
|
||||||
if (ptid == null_ptid || ptid == minus_one_ptid)
|
if (tp == nullptr)
|
||||||
error (_("No selected thread."));
|
{
|
||||||
|
gdb_printf (_("The program being debugged is not being run.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
thread_info *tp = find_thread_ptid (proc_target, ptid);
|
switch_to_thread (tp);
|
||||||
|
|
||||||
if (tp->state == THREAD_EXITED)
|
gdb_printf (_("Last stopped for thread %s (%s).\n"),
|
||||||
error (_("Invalid selected thread."));
|
print_thread_id (tp),
|
||||||
else if (tp->state == THREAD_RUNNING)
|
target_pid_to_str (tp->ptid).c_str ());
|
||||||
error (_("Selected thread is running."));
|
|
||||||
|
|
||||||
bs = tp->control.stop_bpstat;
|
if (tp->state == THREAD_EXITED)
|
||||||
stat = bpstat_num (&bs, &num);
|
{
|
||||||
|
gdb_printf (_("Thread has since exited.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tp->state == THREAD_RUNNING)
|
||||||
|
{
|
||||||
|
gdb_printf (_("Thread is now running.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int num;
|
||||||
|
bpstat *bs = tp->control.stop_bpstat;
|
||||||
|
int stat = bpstat_num (&bs, &num);
|
||||||
|
|
||||||
target_files_info ();
|
target_files_info ();
|
||||||
gdb_printf (_("Program stopped at %s.\n"),
|
gdb_printf (_("Program stopped at %s.\n"),
|
||||||
@@ -2456,6 +2493,8 @@ kill_command (const char *arg, int from_tty)
|
|||||||
target_kill ();
|
target_kill ();
|
||||||
bfd_cache_close_all ();
|
bfd_cache_close_all ();
|
||||||
|
|
||||||
|
update_previous_thread ();
|
||||||
|
|
||||||
if (print_inferior_events)
|
if (print_inferior_events)
|
||||||
gdb_printf (_("[Inferior %d (%s) killed]\n"),
|
gdb_printf (_("[Inferior %d (%s) killed]\n"),
|
||||||
infnum, pid_str.c_str ());
|
infnum, pid_str.c_str ());
|
||||||
@@ -2816,6 +2855,8 @@ detach_command (const char *args, int from_tty)
|
|||||||
|
|
||||||
target_detach (current_inferior (), from_tty);
|
target_detach (current_inferior (), from_tty);
|
||||||
|
|
||||||
|
update_previous_thread ();
|
||||||
|
|
||||||
/* The current inferior process was just detached successfully. Get
|
/* The current inferior process was just detached successfully. Get
|
||||||
rid of breakpoints that no longer make sense. Note we don't do
|
rid of breakpoints that no longer make sense. Note we don't do
|
||||||
this within target_detach because that is also used when
|
this within target_detach because that is also used when
|
||||||
@@ -2854,6 +2895,7 @@ disconnect_command (const char *args, int from_tty)
|
|||||||
target_disconnect (args, from_tty);
|
target_disconnect (args, from_tty);
|
||||||
no_shared_libraries (nullptr, from_tty);
|
no_shared_libraries (nullptr, from_tty);
|
||||||
init_thread_list ();
|
init_thread_list ();
|
||||||
|
update_previous_thread ();
|
||||||
if (deprecated_detach_hook)
|
if (deprecated_detach_hook)
|
||||||
deprecated_detach_hook ();
|
deprecated_detach_hook ();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,13 +168,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;
|
||||||
});
|
});
|
||||||
@@ -184,7 +184,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);
|
||||||
@@ -204,7 +204,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);
|
||||||
|
|
||||||
|
|||||||
@@ -425,9 +425,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
|
||||||
@@ -629,6 +628,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);
|
||||||
|
|||||||
823
gdb/infrun.c
823
gdb/infrun.c
File diff suppressed because it is too large
Load Diff
@@ -117,6 +117,13 @@ enum exec_direction_kind
|
|||||||
/* The current execution direction. */
|
/* The current execution direction. */
|
||||||
extern enum exec_direction_kind execution_direction;
|
extern enum exec_direction_kind execution_direction;
|
||||||
|
|
||||||
|
/* Call this to point 'previous_thread' at the thread returned by
|
||||||
|
inferior_thread, or at nullptr, if there's no selected thread. */
|
||||||
|
extern void update_previous_thread ();
|
||||||
|
|
||||||
|
/* Get a weak reference to 'previous_thread'. */
|
||||||
|
extern thread_info *get_previous_thread ();
|
||||||
|
|
||||||
extern void start_remote (int from_tty);
|
extern void start_remote (int from_tty);
|
||||||
|
|
||||||
/* Clear out all variables saying what to do when inferior is
|
/* Clear out all variables saying what to do when inferior is
|
||||||
|
|||||||
384
gdb/linux-nat.c
384
gdb/linux-nat.c
@@ -256,6 +256,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,20 +910,17 @@ 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_lwp (lp->ptid);
|
delete_lwp (lp->ptid);
|
||||||
@@ -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,12 @@ 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,
|
if (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
|
detach_one_pid (ws->child_ptid ().lwp (), 0);
|
||||||
about. Detach from it. */
|
|
||||||
|
|
||||||
/* Check in lwp_info::status. */
|
|
||||||
if (WIFSTOPPED (lp->status) && linux_is_extended_waitstatus (lp->status))
|
|
||||||
{
|
|
||||||
int event = linux_ptrace_get_extended_event (lp->status);
|
|
||||||
|
|
||||||
if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
|
|
||||||
{
|
|
||||||
unsigned long child_pid;
|
|
||||||
int ret = ptrace (PTRACE_GETEVENTMSG, lp->ptid.lwp (), 0, &child_pid);
|
|
||||||
if (ret == 0)
|
|
||||||
detach_one_pid (child_pid, 0);
|
|
||||||
else
|
|
||||||
perror_warning_with_name (_("Failed to detach fork child"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check in lwp_info::waitstatus. */
|
|
||||||
if (lp->waitstatus.kind () == TARGET_WAITKIND_VFORKED
|
|
||||||
|| lp->waitstatus.kind () == TARGET_WAITKIND_FORKED)
|
|
||||||
detach_one_pid (lp->waitstatus.child_ptid ().pid (), 0);
|
|
||||||
|
|
||||||
|
|
||||||
/* Check in thread_info::pending_waitstatus. */
|
|
||||||
thread_info *tp = find_thread_ptid (linux_target, lp->ptid);
|
|
||||||
if (tp->has_pending_waitstatus ())
|
|
||||||
{
|
|
||||||
const target_waitstatus &ws = tp->pending_waitstatus ();
|
|
||||||
|
|
||||||
if (ws.kind () == TARGET_WAITKIND_VFORKED
|
|
||||||
|| ws.kind () == TARGET_WAITKIND_FORKED)
|
|
||||||
detach_one_pid (ws.child_ptid ().pid (), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check in thread_info::pending_follow. */
|
|
||||||
if (tp->pending_follow.kind () == TARGET_WAITKIND_VFORKED
|
|
||||||
|| tp->pending_follow.kind () == TARGET_WAITKIND_FORKED)
|
|
||||||
detach_one_pid (tp->pending_follow.child_ptid ().pid (), 0);
|
|
||||||
|
|
||||||
if (lp->status != 0)
|
|
||||||
linux_nat_debug_printf ("Pending %s for %s on detach.",
|
|
||||||
strsignal (WSTOPSIG (lp->status)),
|
|
||||||
lp->ptid.to_string ().c_str ());
|
|
||||||
|
|
||||||
/* 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 +1684,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 +1845,53 @@ linux_handle_syscall_trap (struct lwp_info *lp, int stopping)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 +1932,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 +1971,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 +2007,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 +2138,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 +2919,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 +3129,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,14 +3141,28 @@ 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 +3200,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 +3384,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,59 +3568,55 @@ 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 ())
|
if (gdb::optional<target_waitstatus> ws = get_pending_child_status (lp))
|
||||||
{
|
{
|
||||||
struct target_waitstatus *ws = &thread->pending_follow;
|
ptid_t child_ptid = ws->child_ptid ();
|
||||||
|
int child_pid = child_ptid.pid ();
|
||||||
|
int child_lwp = child_ptid.lwp ();
|
||||||
|
|
||||||
if (ws->kind () == TARGET_WAITKIND_FORKED
|
kill_one_lwp (child_lwp);
|
||||||
|| ws->kind () == TARGET_WAITKIND_VFORKED)
|
kill_wait_one_lwp (child_lwp);
|
||||||
{
|
|
||||||
ptid_t child_ptid = ws->child_ptid ();
|
|
||||||
int child_pid = child_ptid.pid ();
|
|
||||||
int child_lwp = child_ptid.lwp ();
|
|
||||||
|
|
||||||
kill_one_lwp (child_lwp);
|
/* Let the arch-specific native code know this process is
|
||||||
kill_wait_one_lwp (child_lwp);
|
gone. */
|
||||||
|
if (ws->kind () != TARGET_WAITKIND_THREAD_CLONED)
|
||||||
/* Let the arch-specific native code know this process is
|
linux_target->low_forget_process (child_pid);
|
||||||
gone. */
|
|
||||||
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);
|
||||||
@@ -4432,6 +4488,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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -351,8 +353,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. */
|
||||||
|
|||||||
@@ -658,7 +658,7 @@ nto_procfs_target::files_info ()
|
|||||||
|
|
||||||
gdb_printf ("\tUsing the running image of %s %s via %s.\n",
|
gdb_printf ("\tUsing the running image of %s %s via %s.\n",
|
||||||
inf->attach_flag ? "attached" : "child",
|
inf->attach_flag ? "attached" : "child",
|
||||||
target_pid_to_str (inferior_ptid).c_str (),
|
target_pid_to_str (ptid_t (inf->pid)).c_str (),
|
||||||
(nodestr != NULL) ? nodestr : "local node");
|
(nodestr != NULL) ? nodestr : "local node");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -2533,7 +2527,7 @@ procfs_target::files_info ()
|
|||||||
|
|
||||||
gdb_printf (_("\tUsing the running image of %s %s via /proc.\n"),
|
gdb_printf (_("\tUsing the running image of %s %s via /proc.\n"),
|
||||||
inf->attach_flag? "attached": "child",
|
inf->attach_flag? "attached": "child",
|
||||||
target_pid_to_str (inferior_ptid).c_str ());
|
target_pid_to_str (ptid_t (inf->pid)).c_str ());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make it die. Wait for it to die. Clean up after it. Note: this
|
/* Make it die. Wait for it to die. Clean up after it. Note: this
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
264
gdb/remote.c
264
gdb/remote.c
@@ -314,6 +314,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;
|
||||||
@@ -381,6 +385,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
|
||||||
@@ -417,6 +425,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;
|
||||||
@@ -543,6 +553,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;
|
||||||
@@ -672,6 +684,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;
|
||||||
@@ -765,7 +778,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 ();
|
||||||
@@ -832,6 +845,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 ();
|
||||||
|
|
||||||
@@ -2164,6 +2180,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,
|
||||||
|
|
||||||
@@ -2579,9 +2598,10 @@ 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. */
|
||||||
get_remote_thread_info (thread)->set_resumed ();
|
if (executing)
|
||||||
|
get_remote_thread_info (thread)->set_resumed ();
|
||||||
|
|
||||||
set_executing (this, ptid, executing);
|
set_executing (this, ptid, executing);
|
||||||
set_running (this, ptid, running);
|
set_running (this, ptid, running);
|
||||||
@@ -3978,15 +3998,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)
|
||||||
@@ -4940,6 +4970,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
|
||||||
@@ -5281,7 +5313,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 ();
|
||||||
|
|
||||||
@@ -5318,6 +5351,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 ();
|
||||||
|
|
||||||
|
remote_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,
|
||||||
@@ -5420,6 +5496,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 },
|
||||||
@@ -5514,6 +5592,9 @@ remote_target::remote_query_supported ()
|
|||||||
if (packet_set_cmd_state (PACKET_QThreadEvents) != AUTO_BOOLEAN_FALSE)
|
if (packet_set_cmd_state (PACKET_QThreadEvents) != AUTO_BOOLEAN_FALSE)
|
||||||
remote_query_supported_append (&q, "QThreadEvents+");
|
remote_query_supported_append (&q, "QThreadEvents+");
|
||||||
|
|
||||||
|
if (packet_set_cmd_state (PACKET_QThreadOptions) != AUTO_BOOLEAN_FALSE)
|
||||||
|
remote_query_supported_append (&q, "QThreadOptions+");
|
||||||
|
|
||||||
if (packet_set_cmd_state (PACKET_no_resumed) != AUTO_BOOLEAN_FALSE)
|
if (packet_set_cmd_state (PACKET_no_resumed) != AUTO_BOOLEAN_FALSE)
|
||||||
remote_query_supported_append (&q, "no-resumed+");
|
remote_query_supported_append (&q, "no-resumed+");
|
||||||
|
|
||||||
@@ -5893,16 +5974,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;
|
||||||
@@ -5910,6 +6000,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
|
||||||
@@ -6075,6 +6179,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. */
|
||||||
|
|
||||||
@@ -6561,6 +6671,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
|
||||||
@@ -6723,6 +6835,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
|
||||||
@@ -6807,10 +6921,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7276,22 +7390,22 @@ struct 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)
|
||||||
{
|
{
|
||||||
struct notif_client *notif = ¬if_client_stop;
|
struct 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;
|
||||||
@@ -7299,13 +7413,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);
|
||||||
@@ -7634,6 +7747,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 ();
|
||||||
@@ -8066,7 +8181,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);
|
||||||
@@ -8252,7 +8368,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;
|
||||||
@@ -14472,6 +14588,9 @@ remote_target::thread_events (int enable)
|
|||||||
if (packet_support (PACKET_QThreadEvents) == PACKET_DISABLE)
|
if (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);
|
||||||
@@ -14482,6 +14601,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 ());
|
||||||
@@ -14491,6 +14611,77 @@ 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 (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 (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);
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
*p++ = ';';
|
||||||
|
p += xsnprintf (p, endp - p, "%s", phex_nz (options, sizeof (options)));
|
||||||
|
if (tp->ptid != magic_null_ptid)
|
||||||
|
{
|
||||||
|
*p++ = ':';
|
||||||
|
p = write_ptid (p, endp, tp->ptid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*p++ = '\0';
|
||||||
|
|
||||||
|
putpkt (rs->buf);
|
||||||
|
getpkt (&rs->buf, 0);
|
||||||
|
|
||||||
|
switch (packet_ok (rs->buf,
|
||||||
|
&remote_protocol_packets[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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
show_remote_cmd (const char *args, int from_tty)
|
show_remote_cmd (const char *args, int from_tty)
|
||||||
{
|
{
|
||||||
@@ -15270,6 +15461,9 @@ Show the maximum size of the address (in bits) in a memory packet."), NULL,
|
|||||||
add_packet_config_cmd (&remote_protocol_packets[PACKET_QThreadEvents],
|
add_packet_config_cmd (&remote_protocol_packets[PACKET_QThreadEvents],
|
||||||
"QThreadEvents", "thread-events", 0);
|
"QThreadEvents", "thread-events", 0);
|
||||||
|
|
||||||
|
add_packet_config_cmd (&remote_protocol_packets[PACKET_QThreadOptions],
|
||||||
|
"QThreadOptions", "thread-options", 0);
|
||||||
|
|
||||||
add_packet_config_cmd (&remote_protocol_packets[PACKET_no_resumed],
|
add_packet_config_cmd (&remote_protocol_packets[PACKET_no_resumed],
|
||||||
"N stop reply", "no-resumed-stop-reply", 0);
|
"N stop reply", "no-resumed-stop-reply", 0);
|
||||||
|
|
||||||
|
|||||||
@@ -1088,13 +1088,13 @@ ppc_displaced_step_prepare (gdbarch *arch, thread_info *thread,
|
|||||||
|
|
||||||
static displaced_step_finish_status
|
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 ()
|
||||||
{
|
{
|
||||||
|
|||||||
21
gdb/target.c
21
gdb/target.c
@@ -2502,6 +2502,8 @@ target_pre_inferior (int from_tty)
|
|||||||
|
|
||||||
current_inferior ()->highest_thread_num = 0;
|
current_inferior ()->highest_thread_num = 0;
|
||||||
|
|
||||||
|
update_previous_thread ();
|
||||||
|
|
||||||
agent_capability_invalidate ();
|
agent_capability_invalidate ();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2530,6 +2532,9 @@ target_preopen (int from_tty)
|
|||||||
error (_("Program not killed."));
|
error (_("Program not killed."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Release reference to old previous thread. */
|
||||||
|
update_previous_thread ();
|
||||||
|
|
||||||
/* Calling target_kill may remove the target from the stack. But if
|
/* Calling target_kill may remove the target from the stack. But if
|
||||||
it doesn't (which seems like a win for UDI), remove it now. */
|
it doesn't (which seems like a win for UDI), remove it now. */
|
||||||
/* Leave the exec target, though. The user may be switching from a
|
/* Leave the exec target, though. The user may be switching from a
|
||||||
@@ -2727,6 +2732,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
|
||||||
@@ -4377,6 +4389,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;
|
||||||
|
|||||||
10
gdb/target.h
10
gdb/target.h
@@ -637,6 +637,8 @@ 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);
|
||||||
|
virtual void follow_clone (ptid_t)
|
||||||
|
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 +731,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 ()
|
||||||
@@ -1892,6 +1898,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,16 +31,25 @@ proc catch_follow_exec { } {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
gdb_test "catch exec" \
|
set bpnum ""
|
||||||
{Catchpoint [0-9]+ \(exec\)}
|
gdb_test_multiple "catch exec" "" {
|
||||||
|
-wrap -re "Catchpoint ($::decimal) \\\(exec\\\)" {
|
||||||
|
set bpnum $expect_out(1,string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if {$bpnum == ""} {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
gdb_test_no_output "set follow-exec-mode new"
|
gdb_test_no_output "set follow-exec-mode new"
|
||||||
|
|
||||||
gdb_test "continue" \
|
gdb_test "continue" \
|
||||||
".*hit Catchpoint.*"
|
"Thread 2.1 .*hit Catchpoint $bpnum.*"
|
||||||
|
|
||||||
|
set any "\[^\r\n\]*"
|
||||||
|
|
||||||
gdb_test "info prog" \
|
gdb_test "info prog" \
|
||||||
"No selected thread."
|
"Last stopped for thread 2.1 \\\($any\\\)\\..*It stopped at breakpoint $bpnum\\..*"
|
||||||
}
|
}
|
||||||
|
|
||||||
catch_follow_exec
|
catch_follow_exec
|
||||||
|
|||||||
@@ -212,15 +212,22 @@ set reading_in_symbols_re {(?:\r\nReading in symbols for [^\r\n]*)?}
|
|||||||
# Test the ability to catch a fork, specify that the child be
|
# Test the ability to catch a fork, specify that the child be
|
||||||
# followed, and continue. Make the catchpoint permanent.
|
# followed, and continue. Make the catchpoint permanent.
|
||||||
|
|
||||||
proc_with_prefix catch_fork_child_follow {} {
|
proc_with_prefix catch_fork_child_follow {second_inferior} {
|
||||||
global gdb_prompt
|
global gdb_prompt
|
||||||
global srcfile
|
global srcfile
|
||||||
global reading_in_symbols_re
|
global reading_in_symbols_re
|
||||||
|
|
||||||
|
if { $second_inferior && [use_gdb_stub] } {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if { ![setup] } {
|
if { ![setup] } {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Get rid of the breakpoint at "main".
|
||||||
|
delete_breakpoints
|
||||||
|
|
||||||
set bp_after_fork [gdb_get_line_number "set breakpoint here"]
|
set bp_after_fork [gdb_get_line_number "set breakpoint here"]
|
||||||
|
|
||||||
gdb_test "catch fork" \
|
gdb_test "catch fork" \
|
||||||
@@ -249,6 +256,29 @@ proc_with_prefix catch_fork_child_follow {} {
|
|||||||
"Temporary breakpoint.*, line $bp_after_fork.*" \
|
"Temporary breakpoint.*, line $bp_after_fork.*" \
|
||||||
"set follow-fork child, tbreak"
|
"set follow-fork child, tbreak"
|
||||||
|
|
||||||
|
if {$second_inferior} {
|
||||||
|
gdb_test "add-inferior" "Added inferior 2.*" "add inferior 2"
|
||||||
|
|
||||||
|
gdb_test "inferior 2" "Switching to inferior 2.*"
|
||||||
|
|
||||||
|
gdb_load $::binfile
|
||||||
|
|
||||||
|
# Start it. This should not affect inferior 1, given "set
|
||||||
|
# schedule-multiple off" (default). GDB used to have a bug
|
||||||
|
# where "start" would clear the pending follow fork
|
||||||
|
# information of inferior 1.
|
||||||
|
gdb_test "start" "Starting program.*Temporary breakpoint .*"
|
||||||
|
|
||||||
|
gdb_test "inferior 1" "Switching to inferior 1.*"
|
||||||
|
|
||||||
|
# Verify that the catchpoint is still mentioned in an "info
|
||||||
|
# breakpoints", and further that the catchpoint still shows
|
||||||
|
# the captured process id.
|
||||||
|
gdb_test "info breakpoints" \
|
||||||
|
".*catchpoint.*keep y.*fork, process.*" \
|
||||||
|
"info breakpoints, after starting second inferior"
|
||||||
|
}
|
||||||
|
|
||||||
set expected_re "\\\[Attaching after.* fork to.*\\\[Detaching after fork from"
|
set expected_re "\\\[Attaching after.* fork to.*\\\[Detaching after fork from"
|
||||||
append expected_re ".* at .*$bp_after_fork.*"
|
append expected_re ".* at .*$bp_after_fork.*"
|
||||||
gdb_test "continue" $expected_re "set follow-fork child, hit tbreak"
|
gdb_test "continue" $expected_re "set follow-fork child, hit tbreak"
|
||||||
@@ -431,6 +461,8 @@ foreach_with_prefix follow-fork-mode {"parent" "child"} {
|
|||||||
|
|
||||||
# Catchpoint tests.
|
# Catchpoint tests.
|
||||||
|
|
||||||
catch_fork_child_follow
|
foreach_with_prefix second_inferior {false true} {
|
||||||
|
catch_fork_child_follow $second_inferior
|
||||||
|
}
|
||||||
catch_fork_unpatch_child
|
catch_fork_unpatch_child
|
||||||
tcatch_fork_parent_follow
|
tcatch_fork_parent_follow
|
||||||
|
|||||||
66
gdb/testsuite/gdb.base/info-program.c
Normal file
66
gdb/testsuite/gdb.base/info-program.c
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/* This testcase is part of GDB, the GNU debugger.
|
||||||
|
|
||||||
|
Copyright 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/>. */
|
||||||
|
|
||||||
|
#ifdef USE_THREADS
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
pthread_barrier_t barrier;
|
||||||
|
pthread_t child_thread;
|
||||||
|
|
||||||
|
void *
|
||||||
|
child_function (void *arg)
|
||||||
|
{
|
||||||
|
pthread_barrier_wait (&barrier);
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
usleep (100);
|
||||||
|
|
||||||
|
pthread_exit (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* USE_THREADS */
|
||||||
|
|
||||||
|
static void
|
||||||
|
done (void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (void)
|
||||||
|
{
|
||||||
|
#ifdef USE_THREADS
|
||||||
|
int res;
|
||||||
|
|
||||||
|
alarm (300);
|
||||||
|
|
||||||
|
pthread_barrier_init (&barrier, NULL, 2);
|
||||||
|
|
||||||
|
res = pthread_create (&child_thread, NULL, child_function, NULL);
|
||||||
|
pthread_barrier_wait (&barrier);
|
||||||
|
#endif /* USE_THREADS */
|
||||||
|
|
||||||
|
done ();
|
||||||
|
|
||||||
|
#ifdef USE_THREADS
|
||||||
|
pthread_join (child_thread, NULL);
|
||||||
|
#endif /* USE_THREADS */
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -13,31 +13,124 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
standard_testfile normal.c
|
# Test "info program".
|
||||||
|
#
|
||||||
|
# We build both single-threaded and multi-threaded programs so that if
|
||||||
|
# the target doesn't support multi-threading, we still exercise the
|
||||||
|
# command.
|
||||||
|
#
|
||||||
|
# With the multi-threaded program, we test that in all-stop mode, GDB
|
||||||
|
# prints information about the last thread that stopped, not the
|
||||||
|
# current thread. In non-stop mode, the command always prints info
|
||||||
|
# about the selected thread, so we test that.
|
||||||
|
|
||||||
if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
|
standard_testfile
|
||||||
return -1
|
|
||||||
|
# Run the test with the given parameters:
|
||||||
|
#
|
||||||
|
# - THREADS: threads flavor, either single-threaded or
|
||||||
|
# multi-threaded.
|
||||||
|
# - NON-STOP: "set non-stop" value, "on" or "off".
|
||||||
|
|
||||||
|
proc do_test { threads non-stop } {
|
||||||
|
save_vars { ::GDBFLAGS } {
|
||||||
|
append ::GDBFLAGS " -ex \"set non-stop ${non-stop}\""
|
||||||
|
clean_restart $::binfile-$threads
|
||||||
|
}
|
||||||
|
|
||||||
|
gdb_test "info program" \
|
||||||
|
"The program being debugged is not being run." \
|
||||||
|
"info program before run"
|
||||||
|
|
||||||
|
if { ![runto done] } {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if {${non-stop} == "on"} {
|
||||||
|
set thread_line "Selected"
|
||||||
|
} else {
|
||||||
|
set thread_line "Last stopped for"
|
||||||
|
}
|
||||||
|
|
||||||
|
gdb_test "info program" \
|
||||||
|
[multi_line \
|
||||||
|
"$thread_line thread 1 (\[^\r\n\]+)\\." \
|
||||||
|
".*" \
|
||||||
|
"Program stopped at $::hex\." \
|
||||||
|
"It stopped at breakpoint $::decimal\." \
|
||||||
|
"Type \"info stack\" or \"info registers\" for more information\."] \
|
||||||
|
"info program after run to main"
|
||||||
|
|
||||||
|
# We don't really care where this step lands, so long as GDB reports
|
||||||
|
# that the inferior stopped due to a step in the subsequent test.
|
||||||
|
gdb_test "next" ".*" "step before info program"
|
||||||
|
|
||||||
|
gdb_test "info program" \
|
||||||
|
[multi_line \
|
||||||
|
"$thread_line thread 1 (\[^\r\n\]+)\\." \
|
||||||
|
".*" \
|
||||||
|
"Program stopped at $::hex\." \
|
||||||
|
"It stopped after being stepped\." \
|
||||||
|
"Type \"info stack\" or \"info registers\" for more information\."] \
|
||||||
|
"info program after next"
|
||||||
|
|
||||||
|
if {$threads == "mt"} {
|
||||||
|
gdb_test "thread 2" "\\\[Switching to thread 2 .*"
|
||||||
|
|
||||||
|
if {${non-stop} == "on"} {
|
||||||
|
gdb_test "info program" \
|
||||||
|
[multi_line \
|
||||||
|
"$thread_line thread 2 (\[^\r\n\]+)\\." \
|
||||||
|
"Selected thread is running\\."] \
|
||||||
|
"info program after next, other thread"
|
||||||
|
} else {
|
||||||
|
gdb_test "info program" \
|
||||||
|
[multi_line \
|
||||||
|
"$thread_line thread 1 (\[^\r\n\]+)\\." \
|
||||||
|
".*" \
|
||||||
|
"Program stopped at $::hex\." \
|
||||||
|
"It stopped after being stepped\." \
|
||||||
|
"Type \"info stack\" or \"info registers\" for more information\."] \
|
||||||
|
"info program after next, other thread"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gdb_test "kill" "" "kill program" \
|
||||||
|
"Kill the program being debugged.*y or n. $" "y"
|
||||||
|
|
||||||
|
gdb_test "info program" "The program being debugged is not being run." \
|
||||||
|
"info program, after kill"
|
||||||
|
|
||||||
|
if { ![runto done] } {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_breakpoints
|
||||||
|
|
||||||
|
gdb_test "info program" \
|
||||||
|
[multi_line \
|
||||||
|
"$thread_line thread 1 (\[^\r\n\]+)\\." \
|
||||||
|
".*" \
|
||||||
|
"Program stopped at $::hex\." \
|
||||||
|
"It stopped at a breakpoint that has since been deleted\." \
|
||||||
|
"Type \"info stack\" or \"info registers\" for more information\."] \
|
||||||
|
"info program after deleting all breakpoints"
|
||||||
}
|
}
|
||||||
|
|
||||||
if { ![runto_main] } {
|
# Build executables and test them, one for each
|
||||||
return -1
|
# single-thread/multi-thread flavor.
|
||||||
|
foreach_with_prefix threads {st mt} {
|
||||||
|
set opts {debug}
|
||||||
|
if {$threads == "mt"} {
|
||||||
|
lappend opts pthreads "additional_flags=-DUSE_THREADS"
|
||||||
|
}
|
||||||
|
|
||||||
|
if { [build_executable "failed to prepare $threads" \
|
||||||
|
${testfile}-${threads} ${srcfile} $opts] } {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach_with_prefix non-stop {on off} {
|
||||||
|
do_test ${threads} ${non-stop}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gdb_test "info program" "Program stopped at $hex\.\r\nIt stopped at breakpoint $decimal\.\r\nType \"info stack\" or \"info registers\" for more information\." \
|
|
||||||
"info program after run to main"
|
|
||||||
|
|
||||||
# We don't really care where this step lands, so long as GDB reports
|
|
||||||
# that the inferior stopped due to a step in the subsequent test.
|
|
||||||
gdb_test "next" ".*" "step before info program"
|
|
||||||
|
|
||||||
gdb_test "info program" "Program stopped at $hex\.\r\nIt stopped after being stepped\.\r\nType \"info stack\" or \"info registers\" for more information\." \
|
|
||||||
"info program after next"
|
|
||||||
|
|
||||||
if {![runto_main]} {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
delete_breakpoints
|
|
||||||
|
|
||||||
gdb_test "info program" "Program stopped at $hex\.\r\nIt stopped at a breakpoint that has since been deleted\.\r\nType \"info stack\" or \"info registers\" for more information\." \
|
|
||||||
"info program after deleting all breakpoints"
|
|
||||||
|
|||||||
@@ -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)"
|
||||||
}
|
}
|
||||||
|
|||||||
84
gdb/testsuite/gdb.threads/foll-fork-other-thread.c
Normal file
84
gdb/testsuite/gdb.threads/foll-fork-other-thread.c
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/* This testcase is part of GDB, the GNU debugger.
|
||||||
|
|
||||||
|
Copyright 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 <unistd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
/* Set by GDB. */
|
||||||
|
volatile int stop_looping = 0;
|
||||||
|
|
||||||
|
static void *
|
||||||
|
gdb_forker_thread (void *arg)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
int stat;
|
||||||
|
pid_t pid = FORK_FUNC ();
|
||||||
|
|
||||||
|
if (pid == 0)
|
||||||
|
_exit (0);
|
||||||
|
|
||||||
|
assert (pid > 0);
|
||||||
|
|
||||||
|
/* Wait for child to exit. */
|
||||||
|
do
|
||||||
|
{
|
||||||
|
ret = waitpid (pid, &stat, 0);
|
||||||
|
}
|
||||||
|
while (ret == -1 && errno == EINTR);
|
||||||
|
|
||||||
|
assert (ret == pid);
|
||||||
|
assert (WIFEXITED (stat));
|
||||||
|
assert (WEXITSTATUS (stat) == 0);
|
||||||
|
|
||||||
|
stop_looping = 1;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sleep_a_bit (void)
|
||||||
|
{
|
||||||
|
usleep (1000 * 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int ret;
|
||||||
|
pthread_t thread;
|
||||||
|
|
||||||
|
alarm (60);
|
||||||
|
|
||||||
|
ret = pthread_create (&thread, NULL, gdb_forker_thread, NULL);
|
||||||
|
assert (ret == 0);
|
||||||
|
|
||||||
|
while (!stop_looping) /* while loop */
|
||||||
|
{
|
||||||
|
sleep_a_bit (); /* break here */
|
||||||
|
sleep_a_bit (); /* other line */
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_join (thread, NULL);
|
||||||
|
|
||||||
|
return 0; /* exiting here */
|
||||||
|
}
|
||||||
172
gdb/testsuite/gdb.threads/foll-fork-other-thread.exp
Normal file
172
gdb/testsuite/gdb.threads/foll-fork-other-thread.exp
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
# Copyright 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 catching a vfork/fork in one thread, and then doing a "next" in
|
||||||
|
# another thread, in different combinations of "set follow-fork
|
||||||
|
# parent/child", and other execution modes.
|
||||||
|
|
||||||
|
standard_testfile
|
||||||
|
|
||||||
|
# Line where to stop the main thread.
|
||||||
|
set break_here_line [gdb_get_line_number "break here"]
|
||||||
|
|
||||||
|
# Build executables, one for each fork flavor.
|
||||||
|
foreach_with_prefix fork_func {fork vfork} {
|
||||||
|
set opts [list debug pthreads additional_flags=-DFORK_FUNC=${fork_func}]
|
||||||
|
if { [build_executable "failed to prepare" \
|
||||||
|
${testfile}-${fork_func} ${srcfile} $opts] } {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run the test with the given parameters:
|
||||||
|
#
|
||||||
|
# - FORK_FUNC: fork flavor, "fork" or "vfork".
|
||||||
|
# - FOLLOW: "set follow-fork" value, either "parent" or "child".
|
||||||
|
# - TARGET-NON-STOP: "maintenance set target-non-stop" value, "auto", "on" or
|
||||||
|
# "off".
|
||||||
|
# - NON-STOP: "set non-stop" value, "on" or "off".
|
||||||
|
# - DISPLACED-STEPPING: "set displaced-stepping" value, "auto", "on" or "off".
|
||||||
|
|
||||||
|
proc do_test { fork_func follow target-non-stop non-stop displaced-stepping } {
|
||||||
|
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}-${fork_func}
|
||||||
|
}
|
||||||
|
|
||||||
|
gdb_test_no_output "set displaced-stepping ${displaced-stepping}"
|
||||||
|
|
||||||
|
if { ![runto_main] } {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_breakpoints
|
||||||
|
|
||||||
|
gdb_test "catch $fork_func" "Catchpoint .*"
|
||||||
|
|
||||||
|
# Verify that the catchpoint is mentioned in an "info breakpoints",
|
||||||
|
# and further that the catchpoint mentions no process id.
|
||||||
|
gdb_test "info breakpoints" \
|
||||||
|
".*catchpoint.*keep y.*fork\[\r\n\]+" \
|
||||||
|
"info breakpoints before fork"
|
||||||
|
|
||||||
|
gdb_test "continue" \
|
||||||
|
"Catchpoint \[0-9\]* \\(.?forked process \[0-9\]*\\),.*" \
|
||||||
|
"explicit child follow, catch fork"
|
||||||
|
|
||||||
|
# Verify that the catchpoint is mentioned in an "info breakpoints",
|
||||||
|
# and further that the catchpoint managed to capture a process id.
|
||||||
|
gdb_test "info breakpoints" \
|
||||||
|
".*catchpoint.*keep y.*fork, process.*" \
|
||||||
|
"info breakpoints after fork"
|
||||||
|
|
||||||
|
gdb_test "thread 1" "Switching to .*"
|
||||||
|
|
||||||
|
gdb_test_no_output "set scheduler-locking on"
|
||||||
|
|
||||||
|
# Advance the next-ing thread to the point where we'll execute the
|
||||||
|
# next.
|
||||||
|
gdb_test "break $::srcfile:$::break_here_line" "Breakpoint $::decimal at $::hex.*"
|
||||||
|
gdb_test "continue" "hit Breakpoint $::decimal, main.*"
|
||||||
|
|
||||||
|
# Disable schedlock and step. The pending fork should no longer
|
||||||
|
# be pending afterwards.
|
||||||
|
|
||||||
|
gdb_test "set scheduler-locking off"
|
||||||
|
|
||||||
|
# Make sure GDB doesn't try to step over the breakpoint at PC
|
||||||
|
# first, we want to make sure that GDB doesn't lose focus of the
|
||||||
|
# step/next in this thread. A breakpoint would make GDB switch
|
||||||
|
# focus anyhow, thus hide a potential bug.
|
||||||
|
delete_breakpoints
|
||||||
|
|
||||||
|
gdb_test_no_output "set follow-fork $follow"
|
||||||
|
|
||||||
|
set any "\[^\r\n\]*"
|
||||||
|
|
||||||
|
if {$follow == "child"} {
|
||||||
|
|
||||||
|
# For fork, GDB detaches from the parent at follow-fork time.
|
||||||
|
# For vfork, GDB detaches from the parent at child exit/exec
|
||||||
|
# time.
|
||||||
|
if {$fork_func == "fork"} {
|
||||||
|
set detach_parent \
|
||||||
|
[multi_line \
|
||||||
|
"\\\[Detaching after $fork_func from parent process $any\\\]" \
|
||||||
|
"\\\[Inferior 1 $any detached\\\]"]
|
||||||
|
} else {
|
||||||
|
set detach_parent ""
|
||||||
|
}
|
||||||
|
|
||||||
|
gdb_test "next" \
|
||||||
|
[multi_line \
|
||||||
|
"\\\[Attaching after $any $fork_func to child $any\\\]" \
|
||||||
|
"\\\[New inferior 2 $any\\\]" \
|
||||||
|
"$detach_parent.*warning: Not resuming: switched threads before following fork child\\." \
|
||||||
|
"\\\[Switching to $any\\\]" \
|
||||||
|
".*"] \
|
||||||
|
"next aborts resumption"
|
||||||
|
|
||||||
|
# The child should be stopped inside the fork implementation
|
||||||
|
# in the runtime. Exactly at which instruction/function is
|
||||||
|
# system dependent, but we can check that our
|
||||||
|
# "gdb_forker_thread" function appears in the backtrace.
|
||||||
|
gdb_test "bt" " in gdb_forker_thread ${any} at ${any}${::srcfile}:.*"
|
||||||
|
|
||||||
|
# The child is now thread 1.
|
||||||
|
gdb_test "print \$_thread" " = 1"
|
||||||
|
|
||||||
|
if {$fork_func == "fork"} {
|
||||||
|
gdb_test "continue" \
|
||||||
|
[multi_line \
|
||||||
|
"Continuing." \
|
||||||
|
"\\\[Inferior 2 \\\(process $any\\\) exited normally\\\]"] \
|
||||||
|
"continue to exit"
|
||||||
|
} else {
|
||||||
|
gdb_test "continue" \
|
||||||
|
[multi_line \
|
||||||
|
"Continuing." \
|
||||||
|
"\\\[Detaching vfork parent process $any after child exit\\\]" \
|
||||||
|
"\\\[Inferior 1 \\\(process $any\\\) detached\\\]" \
|
||||||
|
"\\\[Inferior 2 \\\(process $any\\\) exited normally\\\]"] \
|
||||||
|
"continue to exit"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gdb_test "next" \
|
||||||
|
"\\\[Detaching after $fork_func from child process ${any}\\\].* other line .*" \
|
||||||
|
"next to other line"
|
||||||
|
|
||||||
|
gdb_test "print \$_thread" " = 1"
|
||||||
|
|
||||||
|
gdb_test "continue" \
|
||||||
|
[multi_line \
|
||||||
|
"Continuing." \
|
||||||
|
"\\\[Inferior 1 \\\(process $any\\\) exited normally\\\]"] \
|
||||||
|
"continue to exit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach_with_prefix fork_func {fork vfork} {
|
||||||
|
foreach_with_prefix follow {child} {
|
||||||
|
foreach_with_prefix target-non-stop {auto on off} {
|
||||||
|
foreach_with_prefix non-stop {off} {
|
||||||
|
foreach_with_prefix displaced-stepping {auto on off} {
|
||||||
|
do_test ${fork_func} ${follow} ${target-non-stop} ${non-stop} ${displaced-stepping}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
151
gdb/testsuite/gdb.threads/step-over-thread-exit.exp
Normal file
151
gdb/testsuite/gdb.threads/step-over-thread-exit.exp
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# 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 $cmd \
|
||||||
|
"Command aborted, thread exited\\." \
|
||||||
|
"command aborts when thread exits"
|
||||||
|
}
|
||||||
|
} 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 $cmd \
|
||||||
|
"Command aborted, thread exited\\." \
|
||||||
|
"command aborts when thread exits"
|
||||||
|
} 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 clone" "" {
|
||||||
|
-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\\), .*"
|
||||||
|
|
||||||
|
# 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 \\(\\)\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 \\(\\)\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 \\(\\)\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 \\(\\)\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}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -192,7 +192,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))
|
||||||
@@ -211,7 +212,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;
|
||||||
@@ -235,7 +251,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
|
||||||
@@ -398,6 +414,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)
|
||||||
{
|
{
|
||||||
@@ -450,20 +484,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 ())
|
||||||
{
|
{
|
||||||
@@ -479,16 +515,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 *
|
||||||
|
|||||||
@@ -359,21 +359,47 @@ CHECK_VALID (true, bool, NF (1) == char (1))
|
|||||||
|
|
||||||
enum test_flag
|
enum test_flag
|
||||||
{
|
{
|
||||||
FLAG1 = 1 << 1,
|
FLAG1 = 1 << 0,
|
||||||
FLAG2 = 1 << 2,
|
FLAG2 = 1 << 1,
|
||||||
FLAG3 = 1 << 3,
|
FLAG3 = 1 << 2,
|
||||||
|
FLAG4 = 1 << 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum test_uflag : unsigned
|
enum test_uflag : unsigned
|
||||||
{
|
{
|
||||||
UFLAG1 = 1 << 1,
|
UFLAG1 = 1 << 0,
|
||||||
UFLAG2 = 1 << 2,
|
UFLAG2 = 1 << 1,
|
||||||
UFLAG3 = 1 << 3,
|
UFLAG3 = 1 << 2,
|
||||||
|
UFLAG4 = 1 << 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
DEF_ENUM_FLAGS_TYPE (test_flag, test_flags);
|
DEF_ENUM_FLAGS_TYPE (test_flag, test_flags);
|
||||||
DEF_ENUM_FLAGS_TYPE (test_uflag, test_uflags);
|
DEF_ENUM_FLAGS_TYPE (test_uflag, test_uflags);
|
||||||
|
|
||||||
|
/* to_string enumerator->string mapping functions used to test
|
||||||
|
enum_flags::to_string. These intentionally miss mapping a couple
|
||||||
|
enumerators each (xFLAG2, xFLAG4). */
|
||||||
|
|
||||||
|
static std::string
|
||||||
|
to_string_flags (test_flags flags)
|
||||||
|
{
|
||||||
|
static constexpr test_flags::string_mapping mapping[] = {
|
||||||
|
MAP_ENUM_FLAG (FLAG1),
|
||||||
|
MAP_ENUM_FLAG (FLAG3),
|
||||||
|
};
|
||||||
|
return flags.to_string (mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string
|
||||||
|
to_string_uflags (test_uflags flags)
|
||||||
|
{
|
||||||
|
static constexpr test_uflags::string_mapping mapping[] = {
|
||||||
|
MAP_ENUM_FLAG (UFLAG1),
|
||||||
|
MAP_ENUM_FLAG (UFLAG3),
|
||||||
|
};
|
||||||
|
return flags.to_string (mapping);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
self_test ()
|
self_test ()
|
||||||
{
|
{
|
||||||
@@ -581,6 +607,37 @@ self_test ()
|
|||||||
|
|
||||||
SELF_CHECK (ok);
|
SELF_CHECK (ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Check string conversion. */
|
||||||
|
{
|
||||||
|
SELF_CHECK (to_string_uflags (0)
|
||||||
|
== "0x0 []");
|
||||||
|
SELF_CHECK (to_string_uflags (UFLAG1)
|
||||||
|
== "0x1 [UFLAG1]");
|
||||||
|
SELF_CHECK (to_string_uflags (UFLAG1 | UFLAG3)
|
||||||
|
== "0x5 [UFLAG1 UFLAG3]");
|
||||||
|
SELF_CHECK (to_string_uflags (UFLAG1 | UFLAG2 | UFLAG3)
|
||||||
|
== "0x7 [UFLAG1 UFLAG3 0x2]");
|
||||||
|
SELF_CHECK (to_string_uflags (UFLAG2)
|
||||||
|
== "0x2 [0x2]");
|
||||||
|
/* Check that even with multiple unmapped flags, we only print one
|
||||||
|
unmapped hex number (0xa, in this case). */
|
||||||
|
SELF_CHECK (to_string_uflags (UFLAG1 | UFLAG2 | UFLAG3 | UFLAG4)
|
||||||
|
== "0xf [UFLAG1 UFLAG3 0xa]");
|
||||||
|
|
||||||
|
SELF_CHECK (to_string_flags (0)
|
||||||
|
== "0x0 []");
|
||||||
|
SELF_CHECK (to_string_flags (FLAG1)
|
||||||
|
== "0x1 [FLAG1]");
|
||||||
|
SELF_CHECK (to_string_flags (FLAG1 | FLAG3)
|
||||||
|
== "0x5 [FLAG1 FLAG3]");
|
||||||
|
SELF_CHECK (to_string_flags (FLAG1 | FLAG2 | FLAG3)
|
||||||
|
== "0x7 [FLAG1 FLAG3 0x2]");
|
||||||
|
SELF_CHECK (to_string_flags (FLAG2)
|
||||||
|
== "0x2 [0x2]");
|
||||||
|
SELF_CHECK (to_string_flags (FLAG1 | FLAG2 | FLAG3 | FLAG4)
|
||||||
|
== "0xf [FLAG1 FLAG3 0xa]");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace enum_flags_tests */
|
} /* namespace enum_flags_tests */
|
||||||
|
|||||||
@@ -611,21 +611,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 (),
|
||||||
@@ -2157,7 +2149,7 @@ windows_nat_target::files_info ()
|
|||||||
|
|
||||||
gdb_printf ("\tUsing the running image of %s %s.\n",
|
gdb_printf ("\tUsing the running image of %s %s.\n",
|
||||||
inf->attach_flag ? "attached" : "child",
|
inf->attach_flag ? "attached" : "child",
|
||||||
target_pid_to_str (inferior_ptid).c_str ());
|
target_pid_to_str (ptid_t (inf->pid)).c_str ());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modify CreateProcess parameters for use of a new separate console.
|
/* Modify CreateProcess parameters for use of a new separate console.
|
||||||
|
|||||||
@@ -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,61 +538,65 @@ 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_of (event_thr).lwp (),
|
||||||
|
new_pid);
|
||||||
|
}
|
||||||
|
|
||||||
ptid = ptid_t (new_pid, new_pid);
|
ptid_t child_ptid = (event != PTRACE_EVENT_CLONE
|
||||||
|
? ptid_t (new_pid, new_pid)
|
||||||
|
: ptid_t (ptid_of (event_thr).pid (), new_pid));
|
||||||
|
|
||||||
threads_debug_printf ("Got fork event from LWP %ld, "
|
lwp_info *child_lwp = add_lwp (child_ptid);
|
||||||
"new child is %d",
|
gdb_assert (child_lwp != NULL);
|
||||||
ptid_of (event_thr).lwp (),
|
child_lwp->stopped = 1;
|
||||||
ptid.pid ());
|
if (event != PTRACE_EVENT_CLONE)
|
||||||
|
child_lwp->must_set_ptrace_flags = 1;
|
||||||
|
child_lwp->status_pending_p = 0;
|
||||||
|
|
||||||
|
thread_info *child_thr = get_lwp_thread (child_lwp);
|
||||||
|
|
||||||
|
/* If we're suspending all threads, leave this one suspended
|
||||||
|
too. If the fork/clone parent is stepping over a breakpoint,
|
||||||
|
all other threads have been suspended already. Leave the
|
||||||
|
child suspended too. */
|
||||||
|
if (stopping_threads == STOPPING_AND_SUSPENDING_THREADS
|
||||||
|
|| event_lwp->bp_reinsert != 0)
|
||||||
|
{
|
||||||
|
threads_debug_printf ("leaving child suspended");
|
||||||
|
child_lwp->suspended = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event_lwp->bp_reinsert != 0
|
||||||
|
&& supports_software_single_step ()
|
||||||
|
&& event == PTRACE_EVENT_VFORK)
|
||||||
|
{
|
||||||
|
/* If we leave single-step breakpoints there, child will
|
||||||
|
hit it, so uninsert single-step breakpoints from parent
|
||||||
|
(and child). Once vfork child is done, reinsert
|
||||||
|
them back to parent. */
|
||||||
|
uninsert_single_step_breakpoints (event_thr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event != PTRACE_EVENT_CLONE)
|
||||||
|
{
|
||||||
/* Add the new process to the tables and clone the breakpoint
|
/* 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
|
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
|
will be detached, since we will need the process object and the
|
||||||
breakpoints to remove any breakpoints from memory when we
|
breakpoints to remove any breakpoints from memory when we
|
||||||
detach, and the client side will access registers. */
|
detach, and the client side will access registers. */
|
||||||
child_proc = add_linux_process (new_pid, 0);
|
process_info *child_proc = add_linux_process (new_pid, 0);
|
||||||
gdb_assert (child_proc != NULL);
|
gdb_assert (child_proc != NULL);
|
||||||
child_lwp = add_lwp (ptid);
|
|
||||||
gdb_assert (child_lwp != NULL);
|
|
||||||
child_lwp->stopped = 1;
|
|
||||||
child_lwp->must_set_ptrace_flags = 1;
|
|
||||||
child_lwp->status_pending_p = 0;
|
|
||||||
child_thr = get_lwp_thread (child_lwp);
|
|
||||||
child_thr->last_resume_kind = resume_stop;
|
|
||||||
child_thr->last_status.set_stopped (GDB_SIGNAL_0);
|
|
||||||
|
|
||||||
/* If we're suspending all threads, leave this one suspended
|
process_info *parent_proc = get_thread_process (event_thr);
|
||||||
too. If the fork/clone parent is stepping over a breakpoint,
|
|
||||||
all other threads have been suspended already. Leave the
|
|
||||||
child suspended too. */
|
|
||||||
if (stopping_threads == STOPPING_AND_SUSPENDING_THREADS
|
|
||||||
|| event_lwp->bp_reinsert != 0)
|
|
||||||
{
|
|
||||||
threads_debug_printf ("leaving child suspended");
|
|
||||||
child_lwp->suspended = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
parent_proc = get_thread_process (event_thr);
|
|
||||||
child_proc->attached = parent_proc->attached;
|
child_proc->attached = parent_proc->attached;
|
||||||
|
|
||||||
if (event_lwp->bp_reinsert != 0
|
|
||||||
&& supports_software_single_step ()
|
|
||||||
&& event == PTRACE_EVENT_VFORK)
|
|
||||||
{
|
|
||||||
/* If we leave single-step breakpoints there, child will
|
|
||||||
hit it, so uninsert single-step breakpoints from parent
|
|
||||||
(and child). Once vfork child is done, reinsert
|
|
||||||
them back to parent. */
|
|
||||||
uninsert_single_step_breakpoints (event_thr);
|
|
||||||
}
|
|
||||||
|
|
||||||
clone_all_breakpoints (child_thr, event_thr);
|
clone_all_breakpoints (child_thr, event_thr);
|
||||||
|
|
||||||
target_desc_up tdesc = allocate_target_description ();
|
target_desc_up tdesc = allocate_target_description ();
|
||||||
@@ -590,88 +605,97 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
|
|||||||
|
|
||||||
/* Clone arch-specific process data. */
|
/* 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
|
|
||||||
breakpoints, the list of single-step breakpoints are cloned
|
|
||||||
from the parent's. Remove them from the child process.
|
|
||||||
In case of vfork, we'll reinsert them back once vforked
|
|
||||||
child is done. */
|
|
||||||
if (event_lwp->bp_reinsert != 0
|
|
||||||
&& supports_software_single_step ())
|
|
||||||
{
|
|
||||||
/* The child process is forked and stopped, so it is safe
|
|
||||||
to access its memory without stopping all other threads
|
|
||||||
from other processes. */
|
|
||||||
delete_single_step_breakpoints (child_thr);
|
|
||||||
|
|
||||||
gdb_assert (has_single_step_breakpoints (event_thr));
|
|
||||||
gdb_assert (!has_single_step_breakpoints (child_thr));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Report the event. */
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
threads_debug_printf
|
/* If the parent thread is doing step-over with single-step
|
||||||
("Got clone event from LWP %ld, new child is LWP %ld",
|
breakpoints, the list of single-step breakpoints are cloned
|
||||||
lwpid_of (event_thr), new_pid);
|
from the parent's. Remove them from the child process.
|
||||||
|
In case of vfork, we'll reinsert them back once vforked
|
||||||
|
child is done. */
|
||||||
|
if (event_lwp->bp_reinsert != 0
|
||||||
|
&& supports_software_single_step ())
|
||||||
|
{
|
||||||
|
/* The child process is forked and stopped, so it is safe
|
||||||
|
to access its memory without stopping all other threads
|
||||||
|
from other processes. */
|
||||||
|
delete_single_step_breakpoints (child_thr);
|
||||||
|
|
||||||
ptid = ptid_t (pid_of (event_thr), new_pid);
|
gdb_assert (has_single_step_breakpoints (event_thr));
|
||||||
new_lwp = add_lwp (ptid);
|
gdb_assert (!has_single_step_breakpoints (child_thr));
|
||||||
|
}
|
||||||
/* 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
|
||||||
return 1;
|
&& (event_thr->thread_options & GDB_THREAD_OPTION_CLONE) == 0)
|
||||||
|
{
|
||||||
|
threads_debug_printf
|
||||||
|
("not reporting clone event from LWP %ld, new child is %ld\n",
|
||||||
|
ptid_of (event_thr).lwp (),
|
||||||
|
new_pid);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Leave the child stopped until GDB processes the parent
|
||||||
|
event. */
|
||||||
|
child_thr->last_resume_kind = resume_stop;
|
||||||
|
child_thr->last_status.set_stopped (GDB_SIGNAL_0);
|
||||||
|
|
||||||
|
/* Report the event. */
|
||||||
|
threads_debug_printf
|
||||||
|
("reporting %s event from LWP %ld, new child is %ld\n",
|
||||||
|
(event == PTRACE_EVENT_FORK ? "fork"
|
||||||
|
: event == PTRACE_EVENT_VFORK ? "vfork"
|
||||||
|
: event == PTRACE_EVENT_CLONE ? "clone"
|
||||||
|
: "???"),
|
||||||
|
ptid_of (event_thr).lwp (),
|
||||||
|
new_pid);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
else if (event == PTRACE_EVENT_VFORK_DONE)
|
else if (event == PTRACE_EVENT_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);
|
||||||
delete_lwp (leader_lp);
|
|
||||||
|
thread_info *leader_thread = get_lwp_thread (leader_lp);
|
||||||
|
if (report_exit_events_for (leader_thread))
|
||||||
|
{
|
||||||
|
mark_lwp_dead (leader_lp, W_EXITCODE (0, 0), true);
|
||||||
|
new_pending_event = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
delete_lwp (leader_lp);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return new_pending_event;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Callback for `find_thread'. Returns the first LWP that is not
|
/* 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,7 +3037,20 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
|||||||
{
|
{
|
||||||
if (WIFEXITED (w))
|
if (WIFEXITED (w))
|
||||||
{
|
{
|
||||||
ourstatus->set_exited (WEXITSTATUS (w));
|
/* If we already have the exit recorded in waitstatus, use
|
||||||
|
it. This will happen when we detect a zombie leader,
|
||||||
|
when we had GDB_THREAD_OPTION_EXIT enabled for it. We
|
||||||
|
want to report its exit as TARGET_WAITKIND_THREAD_EXITED,
|
||||||
|
as the whole process hasn't exited yet. */
|
||||||
|
const target_waitstatus &ws = event_child->waitstatus;
|
||||||
|
if (ws.kind () != TARGET_WAITKIND_IGNORE)
|
||||||
|
{
|
||||||
|
gdb_assert (ws.kind () == TARGET_WAITKIND_EXITED
|
||||||
|
|| ws.kind () == TARGET_WAITKIND_THREAD_EXITED);
|
||||||
|
*ourstatus = ws;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ourstatus->set_exited (WEXITSTATUS (w));
|
||||||
|
|
||||||
threads_debug_printf
|
threads_debug_printf
|
||||||
("ret = %s, exited with retcode %d",
|
("ret = %s, exited with retcode %d",
|
||||||
@@ -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))
|
||||||
lwp->waitstatus.set_exited (WEXITSTATUS (wstat));
|
{
|
||||||
|
if (thread_event)
|
||||||
|
lwp->waitstatus.set_thread_exited (WEXITSTATUS (wstat));
|
||||||
|
else
|
||||||
|
lwp->waitstatus.set_exited (WEXITSTATUS (wstat));
|
||||||
|
}
|
||||||
else if (WIFSIGNALED (wstat))
|
else if (WIFSIGNALED (wstat))
|
||||||
lwp->waitstatus.set_signalled (gdb_signal_from_host (WTERMSIG (wstat)));
|
{
|
||||||
|
gdb_assert (!thread_event);
|
||||||
|
lwp->waitstatus.set_signalled (gdb_signal_from_host (WTERMSIG (wstat)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
gdb_assert_not_reached ("unknown status kind");
|
||||||
|
|
||||||
/* Prevent trying to stop it. */
|
/* 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",
|
||||||
@@ -5894,6 +5954,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
|
||||||
@@ -6136,6 +6204,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
|
||||||
@@ -6921,9 +7015,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"
|
||||||
@@ -236,7 +237,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;
|
||||||
|
|
||||||
@@ -610,6 +612,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
|
||||||
@@ -891,6 +904,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:");
|
||||||
@@ -1223,8 +1344,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);
|
||||||
@@ -1644,9 +1766,10 @@ handle_qxfer_threads_worker (thread_info *thread, struct buffer *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;
|
||||||
|
|
||||||
@@ -2363,6 +2486,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
|
||||||
@@ -2489,6 +2614,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+");
|
||||||
@@ -2928,6 +3061,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;
|
||||||
|
|
||||||
@@ -4606,7 +4740,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.
|
||||||
|
|||||||
@@ -530,6 +530,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 ()
|
||||||
{
|
{
|
||||||
@@ -606,6 +612,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 ()
|
||||||
{
|
{
|
||||||
@@ -808,7 +820,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,6 +277,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 ();
|
||||||
|
|
||||||
@@ -317,6 +320,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 ();
|
||||||
|
|
||||||
@@ -476,13 +482,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 ();
|
||||||
@@ -532,6 +539,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 ()
|
||||||
|
|
||||||
@@ -676,6 +686,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);
|
||||||
|
|
||||||
@@ -695,9 +708,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
int read_inferior_memory (CORE_ADDR memaddr, unsigned char *myaddr, int len);
|
int read_inferior_memory (CORE_ADDR memaddr, unsigned char *myaddr, int len);
|
||||||
|
|||||||
@@ -130,6 +130,17 @@ public:
|
|||||||
typedef E enum_type;
|
typedef E enum_type;
|
||||||
typedef typename enum_underlying_type<enum_type>::type underlying_type;
|
typedef typename enum_underlying_type<enum_type>::type underlying_type;
|
||||||
|
|
||||||
|
/* For to_string. Maps one enumerator of E to a string. */
|
||||||
|
struct string_mapping
|
||||||
|
{
|
||||||
|
E flag;
|
||||||
|
const char *str;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Convenience for to_string implementations, to build a
|
||||||
|
string_mapping array. */
|
||||||
|
#define MAP_ENUM_FLAG(ENUM_FLAG) { ENUM_FLAG, #ENUM_FLAG }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/* Allow default construction. */
|
/* Allow default construction. */
|
||||||
constexpr enum_flags ()
|
constexpr enum_flags ()
|
||||||
@@ -183,6 +194,18 @@ public:
|
|||||||
/* Binary operations involving some unrelated type (which would be a
|
/* Binary operations involving some unrelated type (which would be a
|
||||||
bug) are implemented as non-members, and deleted. */
|
bug) are implemented as non-members, and deleted. */
|
||||||
|
|
||||||
|
/* Convert this object to a std::string, using MAPPING as
|
||||||
|
enumerator-to-string mapping array. This is not meant to be
|
||||||
|
called directly. Instead, enum_flags specializations should have
|
||||||
|
their own to_string function wrapping this one, thus hidding the
|
||||||
|
mapping array from callers.
|
||||||
|
|
||||||
|
Note: this is defined outside the template class so it can use
|
||||||
|
the global operators for enum_type, which are only defined after
|
||||||
|
the template class. */
|
||||||
|
template<size_t N>
|
||||||
|
std::string to_string (const string_mapping (&mapping)[N]) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/* Stored as enum_type because GDB knows to print the bit flags
|
/* Stored as enum_type because GDB knows to print the bit flags
|
||||||
neatly if the enum values look like bit flags. */
|
neatly if the enum values look like bit flags. */
|
||||||
@@ -415,6 +438,49 @@ template <typename enum_type, typename any_type,
|
|||||||
typename = is_enum_flags_enum_type_t<enum_type>>
|
typename = is_enum_flags_enum_type_t<enum_type>>
|
||||||
void operator>> (const enum_flags<enum_type> &, const any_type &) = delete;
|
void operator>> (const enum_flags<enum_type> &, const any_type &) = delete;
|
||||||
|
|
||||||
|
template<typename E>
|
||||||
|
template<size_t N>
|
||||||
|
std::string
|
||||||
|
enum_flags<E>::to_string (const string_mapping (&mapping)[N]) const
|
||||||
|
{
|
||||||
|
enum_type flags = raw ();
|
||||||
|
std::string res = hex_string (flags);
|
||||||
|
res += " [";
|
||||||
|
|
||||||
|
bool need_space = false;
|
||||||
|
for (const auto &entry : mapping)
|
||||||
|
{
|
||||||
|
if ((flags & entry.flag) != 0)
|
||||||
|
{
|
||||||
|
/* Work with an unsigned version of the underlying type,
|
||||||
|
because if enum_type's underlying type is signed, op~
|
||||||
|
won't be defined for it, and, bitwise operations on
|
||||||
|
signed types are implementation defined. */
|
||||||
|
using uns = typename std::make_unsigned<underlying_type>::type;
|
||||||
|
flags &= (enum_type) ~(uns) entry.flag;
|
||||||
|
|
||||||
|
if (need_space)
|
||||||
|
res += " ";
|
||||||
|
res += entry.str;
|
||||||
|
|
||||||
|
need_space = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If there were flags not included in the mapping, print them as
|
||||||
|
a hex number. */
|
||||||
|
if (flags != 0)
|
||||||
|
{
|
||||||
|
if (need_space)
|
||||||
|
res += " ";
|
||||||
|
res += hex_string (flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
res += "]";
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
#else /* __cplusplus */
|
#else /* __cplusplus */
|
||||||
|
|
||||||
/* In C, the flags type is just a typedef for the enum type. */
|
/* In C, the flags type is just a typedef for the enum type. */
|
||||||
|
|||||||
Reference in New Issue
Block a user