forked from Imagelibrary/binutils-gdb
Compare commits
37 Commits
gdb-15.1-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
|
||||
automatically, or through one of the memory-tag subcommands.
|
||||
|
||||
* Scheduler-locking and new threads
|
||||
|
||||
When scheduler-locking is in effect, only the current thread may run
|
||||
when the inferior is resumed. However, previously, new threads
|
||||
created by the resumed thread would still be able to run free. Now,
|
||||
they are held stopped.
|
||||
|
||||
* "info breakpoints" now displays enabled breakpoint locations of
|
||||
disabled breakpoints as in the "y-" state. For example:
|
||||
|
||||
@@ -153,6 +160,10 @@ set style tui-current-position [on|off]
|
||||
Whether to style the source and assembly code highlighted by the
|
||||
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
|
||||
|
||||
document user-defined
|
||||
@@ -278,6 +289,22 @@ GNU/Linux/CSKY (gdbserver) csky*-*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
|
||||
|
||||
* DBX mode is deprecated, and will be removed in GDB 13
|
||||
|
||||
@@ -233,7 +233,9 @@ annotate_thread_changed (void)
|
||||
/* Emit notification on thread exit. */
|
||||
|
||||
static void
|
||||
annotate_thread_exited (struct thread_info *t, int silent)
|
||||
annotate_thread_exited (thread_info *t,
|
||||
gdb::optional<ULONGEST> exit_code,
|
||||
bool /* silent */)
|
||||
{
|
||||
if (annotation_level > 1)
|
||||
{
|
||||
|
||||
@@ -3237,7 +3237,9 @@ remove_breakpoints (void)
|
||||
that thread. */
|
||||
|
||||
static void
|
||||
remove_threaded_breakpoints (struct thread_info *tp, int silent)
|
||||
remove_threaded_breakpoints (thread_info *tp,
|
||||
gdb::optional<ULONGEST> exit_code,
|
||||
bool /* silent */)
|
||||
{
|
||||
for (breakpoint *b : all_breakpoints_safe ())
|
||||
{
|
||||
|
||||
@@ -192,10 +192,11 @@ write_memory_ptid (ptid_t ptid, CORE_ADDR memaddr,
|
||||
}
|
||||
|
||||
static bool
|
||||
displaced_step_instruction_executed_successfully (gdbarch *arch,
|
||||
gdb_signal signal)
|
||||
displaced_step_instruction_executed_successfully
|
||||
(gdbarch *arch, const target_waitstatus &status)
|
||||
{
|
||||
if (signal != GDB_SIGNAL_TRAP)
|
||||
if (status.kind () != TARGET_WAITKIND_STOPPED
|
||||
|| status.sig () != GDB_SIGNAL_TRAP)
|
||||
return false;
|
||||
|
||||
if (target_stopped_by_watchpoint ())
|
||||
@@ -210,7 +211,7 @@ displaced_step_instruction_executed_successfully (gdbarch *arch,
|
||||
|
||||
displaced_step_finish_status
|
||||
displaced_step_buffers::finish (gdbarch *arch, thread_info *thread,
|
||||
gdb_signal sig)
|
||||
const target_waitstatus &status)
|
||||
{
|
||||
gdb_assert (thread->displaced_step_state.in_progress ());
|
||||
|
||||
@@ -253,10 +254,17 @@ displaced_step_buffers::finish (gdbarch *arch, thread_info *thread,
|
||||
thread->ptid.to_string ().c_str (),
|
||||
paddress (arch, buffer->addr));
|
||||
|
||||
/* If the thread exited while stepping, we are done. The code above
|
||||
made the buffer available again, and we restored the bytes in the
|
||||
buffer. We don't want to run the fixup: since the thread is now
|
||||
dead there's nothing to adjust. */
|
||||
if (status.kind () == TARGET_WAITKIND_THREAD_EXITED)
|
||||
return DISPLACED_STEP_FINISH_STATUS_OK;
|
||||
|
||||
regcache *rc = get_thread_regcache (thread);
|
||||
|
||||
bool instruction_executed_successfully
|
||||
= displaced_step_instruction_executed_successfully (arch, sig);
|
||||
= displaced_step_instruction_executed_successfully (arch, status);
|
||||
|
||||
if (instruction_executed_successfully)
|
||||
{
|
||||
|
||||
@@ -168,7 +168,7 @@ struct displaced_step_buffers
|
||||
CORE_ADDR &displaced_pc);
|
||||
|
||||
displaced_step_finish_status finish (gdbarch *arch, thread_info *thread,
|
||||
gdb_signal sig);
|
||||
const target_waitstatus &status);
|
||||
|
||||
const displaced_step_copy_insn_closure *
|
||||
copy_insn_closure_by_addr (CORE_ADDR addr);
|
||||
|
||||
@@ -7037,7 +7037,9 @@ the following:
|
||||
There is no locking and any thread may run at any time.
|
||||
|
||||
@item on
|
||||
Only the current thread may run when the inferior is resumed.
|
||||
Only the current thread may run when the inferior is resumed. New
|
||||
threads created by the resumed thread are held stopped at their entry
|
||||
point, before they execute any instruction.
|
||||
|
||||
@item step
|
||||
Behaves like @code{on} when stepping, and @code{off} otherwise.
|
||||
@@ -24077,6 +24079,10 @@ are:
|
||||
@tab @code{QThreadEvents}
|
||||
@tab Tracking thread lifetime.
|
||||
|
||||
@item @code{thread-options}
|
||||
@tab @code{QThreadOptions}
|
||||
@tab Set thread event reporting options.
|
||||
|
||||
@item @code{no-resumed-stop-reply}
|
||||
@tab @code{no resumed thread left stop reply}
|
||||
@tab Tracking thread lifetime.
|
||||
@@ -42108,6 +42114,17 @@ appropriate @samp{qSupported} feature (@pxref{qSupported}). The
|
||||
remote stub must also supply the appropriate @samp{qSupported} feature
|
||||
indicating support.
|
||||
|
||||
@cindex thread clone events, remote reply
|
||||
@anchor{thread clone event}
|
||||
@item clone
|
||||
The packet indicates that @code{clone} was called, and @var{r} is the
|
||||
thread ID of the new child thread, as specified in @ref{thread-id
|
||||
syntax}. This packet is only applicable to targets that support clone
|
||||
events.
|
||||
|
||||
This packet should not be sent by default; @value{GDBN} requests it
|
||||
with the @ref{QThreadOptions} packet.
|
||||
|
||||
@cindex thread create event, remote reply
|
||||
@anchor{thread create event}
|
||||
@item create
|
||||
@@ -42146,9 +42163,10 @@ hex strings.
|
||||
@item w @var{AA} ; @var{tid}
|
||||
|
||||
The thread exited, and @var{AA} is the exit status. This response
|
||||
should not be sent by default; @value{GDBN} requests it with the
|
||||
@ref{QThreadEvents} packet. See also @ref{thread create event} above.
|
||||
@var{AA} is formatted as a big-endian hex string.
|
||||
should not be sent by default; @value{GDBN} requests it with either
|
||||
the @ref{QThreadEvents} or @ref{QThreadOptions} packets. See also
|
||||
@ref{thread create event} above. @var{AA} is formatted as a
|
||||
big-endian hex string.
|
||||
|
||||
@item N
|
||||
There are no resumed threads left in the target. In other words, even
|
||||
@@ -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
|
||||
its @samp{qSupported} reply.
|
||||
|
||||
This packet always enables/disables event reporting for all threads of
|
||||
all processes under control of the remote stub. For per-thread
|
||||
control of optional event reporting, see the @ref{QThreadOptions}
|
||||
packet.
|
||||
|
||||
Reply:
|
||||
@table @samp
|
||||
@item OK
|
||||
@@ -42889,6 +42912,94 @@ the stub.
|
||||
Use of this packet is controlled by the @code{set remote thread-events}
|
||||
command (@pxref{Remote Configuration, set remote thread-events}).
|
||||
|
||||
@anchor{QThreadOptions}
|
||||
@item QThreadOptions@r{[};@var{options}@r{[}:@var{thread-id}@r{]]}@dots{}
|
||||
@cindex thread options, remote request
|
||||
@cindex @samp{QThreadOptions} packet
|
||||
|
||||
For each inferior thread, the last @var{options} in the list with a
|
||||
matching @var{thread-id} are applied. Any options previously set on a
|
||||
thread are discarded and replaced by the new options specified.
|
||||
Threads that do not match any @var{thread-id} retain their
|
||||
previously-set options. Thread IDs are specified using the syntax
|
||||
described in @ref{thread-id syntax}. If multiprocess extensions
|
||||
(@pxref{multiprocess extensions}) are supported, options can be
|
||||
specified to apply to all threads of a process by using the
|
||||
@samp{p@var{pid}.-1} form of @var{thread-id}. Options with no
|
||||
@var{thread-id} apply to all threads. Specifying no options is an
|
||||
error.
|
||||
|
||||
@var{options} is the bitwise @code{OR} of the following values. All
|
||||
values are given in hexadecimal representation.
|
||||
|
||||
@table @code
|
||||
@item GDB_THREAD_OPTION_CLONE (0x1)
|
||||
Report thread clone events (@pxref{thread clone event}). This is only
|
||||
meaningful for targets that support clone events (e.g., GNU/Linux
|
||||
systems).
|
||||
|
||||
@item GDB_THREAD_OPTION_EXIT (0x2)
|
||||
Report thread exit events (@pxref{thread exit event}).
|
||||
@end table
|
||||
|
||||
@noindent
|
||||
|
||||
For example, @value{GDBN} enables the @code{GDB_THREAD_OPTION_EXIT}
|
||||
and @code{GDB_THREAD_OPTION_CLONE} options when single-stepping a
|
||||
thread past a breakpoint, for the following reasons:
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
If the single-stepped thread exits (e.g., it executes a thread exit
|
||||
system call), enabling @code{GDB_THREAD_OPTION_EXIT} prevents
|
||||
@value{GDBN} from waiting forever, not knowing that it should no
|
||||
longer expect a stop for that same thread, and blocking other threads
|
||||
from progressing.
|
||||
|
||||
@item
|
||||
If the single-stepped thread spawns a new clone child (i.e., it
|
||||
executes a clone system call), enabling @code{GDB_THREAD_OPTION_CLONE}
|
||||
halts the cloned thread before it executes any instructions, and thus
|
||||
prevents the following problematic situations:
|
||||
|
||||
@itemize @minus
|
||||
@item
|
||||
If the breakpoint is stepped-over in-line, the spawned thread would
|
||||
incorrectly run free while the breakpoint being stepped over is not
|
||||
inserted, and thus the cloned thread may potentially run past the
|
||||
breakpoint without stopping for it;
|
||||
|
||||
@item
|
||||
If displaced (out-of-line) stepping is used, the cloned thread starts
|
||||
running at the out-of-line PC, leading to undefined behavior, usually
|
||||
crashing or corrupting data.
|
||||
@end itemize
|
||||
|
||||
@end itemize
|
||||
|
||||
New threads start with thread options cleared.
|
||||
|
||||
@value{GDBN} does not enable this feature unless the stub reports that
|
||||
it supports it by including
|
||||
@samp{QThreadOptions=@var{supported_options}} in its @samp{qSupported}
|
||||
reply.
|
||||
|
||||
Reply:
|
||||
@table @samp
|
||||
@item OK
|
||||
The request succeeded.
|
||||
|
||||
@item E @var{nn}
|
||||
An error occurred. The error number @var{nn} is given as hex digits.
|
||||
|
||||
@item @w{}
|
||||
An empty reply indicates that @samp{QThreadOptions} is not supported by
|
||||
the stub.
|
||||
@end table
|
||||
|
||||
Use of this packet is controlled by the @code{set remote thread-options}
|
||||
command (@pxref{Remote Configuration, set remote thread-options}).
|
||||
|
||||
@item qRcmd,@var{command}
|
||||
@cindex execute remote command, remote request
|
||||
@cindex @samp{qRcmd} packet
|
||||
@@ -43334,6 +43445,11 @@ These are the currently defined stub features and their properties:
|
||||
@tab @samp{-}
|
||||
@tab No
|
||||
|
||||
@item @samp{QThreadOptions}
|
||||
@tab Yes
|
||||
@tab @samp{-}
|
||||
@tab No
|
||||
|
||||
@item @samp{no-resumed}
|
||||
@tab No
|
||||
@tab @samp{-}
|
||||
@@ -43555,6 +43671,13 @@ The remote stub reports the supported actions in the reply to
|
||||
@item QThreadEvents
|
||||
The remote stub understands the @samp{QThreadEvents} packet.
|
||||
|
||||
@item QThreadOptions=@var{supported_options}
|
||||
The remote stub understands the @samp{QThreadOptions} packet.
|
||||
@var{supported_options} indicates the set of thread options the remote
|
||||
stub supports. @var{supported_options} has the same format as the
|
||||
@var{options} parameter of the @code{QThreadOptions} packet, described
|
||||
at @ref{QThreadOptions}.
|
||||
|
||||
@item no-resumed
|
||||
The remote stub reports the @samp{N} stop reply.
|
||||
|
||||
|
||||
@@ -1300,9 +1300,6 @@ fbsd_nat_target::wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus,
|
||||
{
|
||||
fbsd_lwp_debug_printf ("deleting thread for LWP %u",
|
||||
pl.pl_lwpid);
|
||||
if (print_thread_events)
|
||||
gdb_printf (_("[%s exited]\n"),
|
||||
target_pid_to_str (wptid).c_str ());
|
||||
low_delete_thread (thr);
|
||||
delete_thread (thr);
|
||||
}
|
||||
|
||||
@@ -1826,10 +1826,14 @@ Throw an exception if any unexpected error happens.
|
||||
Method(
|
||||
comment="""
|
||||
Clean up after a displaced step of THREAD.
|
||||
|
||||
It is possible for the displaced-stepped instruction to have caused
|
||||
the thread to exit. The implementation can detect this case by
|
||||
checking if WS.kind is TARGET_WAITKIND_THREAD_EXITED.
|
||||
""",
|
||||
type="displaced_step_finish_status",
|
||||
name="displaced_step_finish",
|
||||
params=[("thread_info *", "thread"), ("gdb_signal", "sig")],
|
||||
params=[("thread_info *", "thread"), ("const target_waitstatus &", "ws")],
|
||||
predefault="NULL",
|
||||
invalid="(! gdbarch->displaced_step_finish) != (! gdbarch->displaced_step_prepare)",
|
||||
)
|
||||
|
||||
@@ -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 void set_gdbarch_displaced_step_prepare (struct gdbarch *gdbarch, gdbarch_displaced_step_prepare_ftype *displaced_step_prepare);
|
||||
|
||||
/* Clean up after a displaced step of THREAD. */
|
||||
/* Clean up after a displaced step of THREAD.
|
||||
|
||||
typedef displaced_step_finish_status (gdbarch_displaced_step_finish_ftype) (struct gdbarch *gdbarch, thread_info *thread, gdb_signal sig);
|
||||
extern displaced_step_finish_status gdbarch_displaced_step_finish (struct gdbarch *gdbarch, thread_info *thread, gdb_signal sig);
|
||||
It is possible for the displaced-stepped instruction to have caused
|
||||
the thread to exit. The implementation can detect this case by
|
||||
checking if WS.kind is TARGET_WAITKIND_THREAD_EXITED. */
|
||||
|
||||
typedef displaced_step_finish_status (gdbarch_displaced_step_finish_ftype) (struct gdbarch *gdbarch, thread_info *thread, const target_waitstatus &ws);
|
||||
extern displaced_step_finish_status gdbarch_displaced_step_finish (struct gdbarch *gdbarch, thread_info *thread, const target_waitstatus &ws);
|
||||
extern void set_gdbarch_displaced_step_finish (struct gdbarch *gdbarch, gdbarch_displaced_step_finish_ftype *displaced_step_finish);
|
||||
|
||||
/* Return the closure associated to the displaced step buffer that is at ADDR. */
|
||||
|
||||
@@ -4097,13 +4097,13 @@ set_gdbarch_displaced_step_prepare (struct gdbarch *gdbarch,
|
||||
}
|
||||
|
||||
displaced_step_finish_status
|
||||
gdbarch_displaced_step_finish (struct gdbarch *gdbarch, thread_info *thread, gdb_signal sig)
|
||||
gdbarch_displaced_step_finish (struct gdbarch *gdbarch, thread_info *thread, const target_waitstatus &ws)
|
||||
{
|
||||
gdb_assert (gdbarch != NULL);
|
||||
gdb_assert (gdbarch->displaced_step_finish != NULL);
|
||||
if (gdbarch_debug >= 2)
|
||||
gdb_printf (gdb_stdlog, "gdbarch_displaced_step_finish called\n");
|
||||
return gdbarch->displaced_step_finish (gdbarch, thread, sig);
|
||||
return gdbarch->displaced_step_finish (gdbarch, thread, ws);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -28,6 +28,7 @@ struct symtab;
|
||||
#include "ui-out.h"
|
||||
#include "btrace.h"
|
||||
#include "target/waitstatus.h"
|
||||
#include "target/target.h"
|
||||
#include "cli/cli-utils.h"
|
||||
#include "gdbsupport/refcounted-object.h"
|
||||
#include "gdbsupport/common-gdbthread.h"
|
||||
@@ -470,6 +471,17 @@ public:
|
||||
m_thread_fsm = std::move (fsm);
|
||||
}
|
||||
|
||||
/* Record the thread options last set for this thread. */
|
||||
|
||||
void set_thread_options (gdb_thread_options thread_options);
|
||||
|
||||
/* Get the thread options last set for this thread. */
|
||||
|
||||
gdb_thread_options thread_options () const
|
||||
{
|
||||
return m_thread_options;
|
||||
}
|
||||
|
||||
int current_line = 0;
|
||||
struct symtab *current_symtab = NULL;
|
||||
|
||||
@@ -577,6 +589,10 @@ private:
|
||||
left to do for the thread's execution command after the target
|
||||
stops. Several execution commands use it. */
|
||||
std::unique_ptr<struct thread_fsm> m_thread_fsm;
|
||||
|
||||
/* The thread options as last set with a call to
|
||||
target_set_thread_options. */
|
||||
gdb_thread_options m_thread_options;
|
||||
};
|
||||
|
||||
using thread_info_resumed_with_pending_wait_status_node
|
||||
@@ -620,16 +636,30 @@ extern struct thread_info *add_thread_with_info (process_stratum_target *targ,
|
||||
|
||||
/* Delete thread THREAD and notify of thread exit. If the thread is
|
||||
currently not deletable, don't actually delete it but still tag it
|
||||
as exited and do the notification. */
|
||||
extern void delete_thread (struct thread_info *thread);
|
||||
as exited and do the notification. EXIT_CODE is the thread's exit
|
||||
code. If SILENT, don't actually notify the CLI. THREAD must not
|
||||
be NULL or an assertion will fail. */
|
||||
extern void delete_thread_with_exit_code (thread_info *thread,
|
||||
ULONGEST exit_code,
|
||||
bool silent = false);
|
||||
|
||||
/* Delete thread THREAD and notify of thread exit. If the thread is
|
||||
currently not deletable, don't actually delete it but still tag it
|
||||
as exited and do the notification. THREAD must not be NULL or an
|
||||
assertion will fail. */
|
||||
extern void delete_thread (thread_info *thread);
|
||||
|
||||
/* Like delete_thread, but be quiet about it. Used when the process
|
||||
this thread belonged to has already exited, for example. */
|
||||
extern void delete_thread_silent (struct thread_info *thread);
|
||||
|
||||
/* Mark the thread exited, but don't delete it or remove it from the
|
||||
inferior thread list. */
|
||||
extern void set_thread_exited (thread_info *tp, bool silent);
|
||||
inferior thread list. EXIT_CODE is the thread's exit code, if
|
||||
available. If SILENT, then don't inform the CLI about the
|
||||
exit. */
|
||||
extern void set_thread_exited (thread_info *tp,
|
||||
gdb::optional<ULONGEST> exit_code = {},
|
||||
bool silent = false);
|
||||
|
||||
/* Delete a step_resume_breakpoint from the thread database. */
|
||||
extern void delete_step_resume_breakpoint (struct thread_info *);
|
||||
|
||||
@@ -522,7 +522,7 @@ inf_ptrace_target::files_info ()
|
||||
|
||||
gdb_printf (_("\tUsing the running image of %s %s.\n"),
|
||||
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
|
||||
|
||||
84
gdb/infcmd.c
84
gdb/infcmd.c
@@ -1937,37 +1937,74 @@ finish_command (const char *arg, int from_tty)
|
||||
static void
|
||||
info_program_command (const char *args, int from_tty)
|
||||
{
|
||||
bpstat *bs;
|
||||
int num, stat;
|
||||
ptid_t ptid;
|
||||
process_stratum_target *proc_target;
|
||||
scoped_restore_current_thread restore_thread;
|
||||
|
||||
if (!target_has_execution ())
|
||||
{
|
||||
gdb_printf (_("The program being debugged is not being run.\n"));
|
||||
return;
|
||||
}
|
||||
thread_info *tp;
|
||||
|
||||
/* In non-stop, since every thread is controlled individually, we'll
|
||||
show execution info about the current thread. In all-stop, we'll
|
||||
show execution info about the last stop. */
|
||||
|
||||
if (non_stop)
|
||||
{
|
||||
ptid = inferior_ptid;
|
||||
proc_target = current_inferior ()->process_target ();
|
||||
if (!target_has_execution ())
|
||||
{
|
||||
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
|
||||
get_last_target_status (&proc_target, &ptid, nullptr);
|
||||
{
|
||||
tp = get_previous_thread ();
|
||||
|
||||
if (ptid == null_ptid || ptid == minus_one_ptid)
|
||||
error (_("No selected thread."));
|
||||
if (tp == nullptr)
|
||||
{
|
||||
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)
|
||||
error (_("Invalid selected thread."));
|
||||
else if (tp->state == THREAD_RUNNING)
|
||||
error (_("Selected thread is running."));
|
||||
gdb_printf (_("Last stopped for thread %s (%s).\n"),
|
||||
print_thread_id (tp),
|
||||
target_pid_to_str (tp->ptid).c_str ());
|
||||
|
||||
bs = tp->control.stop_bpstat;
|
||||
stat = bpstat_num (&bs, &num);
|
||||
if (tp->state == THREAD_EXITED)
|
||||
{
|
||||
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 ();
|
||||
gdb_printf (_("Program stopped at %s.\n"),
|
||||
@@ -2456,6 +2493,8 @@ kill_command (const char *arg, int from_tty)
|
||||
target_kill ();
|
||||
bfd_cache_close_all ();
|
||||
|
||||
update_previous_thread ();
|
||||
|
||||
if (print_inferior_events)
|
||||
gdb_printf (_("[Inferior %d (%s) killed]\n"),
|
||||
infnum, pid_str.c_str ());
|
||||
@@ -2816,6 +2855,8 @@ detach_command (const char *args, int from_tty)
|
||||
|
||||
target_detach (current_inferior (), from_tty);
|
||||
|
||||
update_previous_thread ();
|
||||
|
||||
/* The current inferior process was just detached successfully. Get
|
||||
rid of breakpoints that no longer make sense. Note we don't do
|
||||
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);
|
||||
no_shared_libraries (nullptr, from_tty);
|
||||
init_thread_list ();
|
||||
update_previous_thread ();
|
||||
if (deprecated_detach_hook)
|
||||
deprecated_detach_hook ();
|
||||
}
|
||||
|
||||
@@ -168,13 +168,13 @@ add_inferior (int pid)
|
||||
/* See inferior.h. */
|
||||
|
||||
void
|
||||
inferior::clear_thread_list (bool silent)
|
||||
inferior::clear_thread_list ()
|
||||
{
|
||||
thread_list.clear_and_dispose ([=] (thread_info *thr)
|
||||
{
|
||||
threads_debug_printf ("deleting thread %s, silent = %d",
|
||||
thr->ptid.to_string ().c_str (), silent);
|
||||
set_thread_exited (thr, silent);
|
||||
threads_debug_printf ("deleting thread %s",
|
||||
thr->ptid.to_string ().c_str ());
|
||||
set_thread_exited (thr, {}, true);
|
||||
if (thr->deletable ())
|
||||
delete thr;
|
||||
});
|
||||
@@ -184,7 +184,7 @@ inferior::clear_thread_list (bool silent)
|
||||
void
|
||||
delete_inferior (struct inferior *inf)
|
||||
{
|
||||
inf->clear_thread_list (true);
|
||||
inf->clear_thread_list ();
|
||||
|
||||
auto it = inferior_list.iterator_to (*inf);
|
||||
inferior_list.erase (it);
|
||||
@@ -204,7 +204,7 @@ delete_inferior (struct inferior *inf)
|
||||
static void
|
||||
exit_inferior_1 (struct inferior *inf, int silent)
|
||||
{
|
||||
inf->clear_thread_list (silent);
|
||||
inf->clear_thread_list ();
|
||||
|
||||
gdb::observers::inferior_exit.notify (inf);
|
||||
|
||||
|
||||
@@ -425,9 +425,8 @@ public:
|
||||
inline safe_inf_threads_range threads_safe ()
|
||||
{ return safe_inf_threads_range (this->thread_list.begin ()); }
|
||||
|
||||
/* Delete all threads in the thread list. If SILENT, exit threads
|
||||
silently. */
|
||||
void clear_thread_list (bool silent);
|
||||
/* Delete all threads in the thread list, silently. */
|
||||
void clear_thread_list ();
|
||||
|
||||
/* Continuations-related methods. A continuation is an std::function
|
||||
to be called to finish the execution of a command when running
|
||||
@@ -629,6 +628,8 @@ extern void detach_inferior (inferior *inf);
|
||||
|
||||
extern void exit_inferior (inferior *inf);
|
||||
|
||||
/* Like exit_inferior, but be quiet -- don't announce the exit of the
|
||||
inferior's threads to the CLI. */
|
||||
extern void exit_inferior_silent (inferior *inf);
|
||||
|
||||
extern void exit_inferior_num_silent (int num);
|
||||
|
||||
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. */
|
||||
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);
|
||||
|
||||
/* 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 ();
|
||||
}
|
||||
|
||||
/* Convert an LWP's pending status to a std::string. */
|
||||
|
||||
static std::string
|
||||
pending_status_str (lwp_info *lp)
|
||||
{
|
||||
gdb_assert (lwp_status_pending_p (lp));
|
||||
|
||||
if (lp->waitstatus.kind () != TARGET_WAITKIND_IGNORE)
|
||||
return lp->waitstatus.to_string ();
|
||||
else
|
||||
return status_to_str (lp->status);
|
||||
}
|
||||
|
||||
/* Return true if we should report exit events for LP. */
|
||||
|
||||
static bool
|
||||
report_exit_events_for (lwp_info *lp)
|
||||
{
|
||||
thread_info *thr = find_thread_ptid (linux_target, lp->ptid);
|
||||
gdb_assert (thr != nullptr);
|
||||
|
||||
return (report_thread_events
|
||||
|| (thr->thread_options () & GDB_THREAD_OPTION_EXIT) != 0);
|
||||
}
|
||||
|
||||
|
||||
/* LWP accessors. */
|
||||
|
||||
@@ -885,20 +910,17 @@ linux_nat_switch_fork (ptid_t new_ptid)
|
||||
registers_changed ();
|
||||
}
|
||||
|
||||
/* Handle the exit of a single thread LP. */
|
||||
/* Handle the exit of a single thread LP. If DEL_THREAD is true,
|
||||
delete the thread_info associated to LP, if it exists. */
|
||||
|
||||
static void
|
||||
exit_lwp (struct lwp_info *lp)
|
||||
exit_lwp (struct lwp_info *lp, bool del_thread = true)
|
||||
{
|
||||
struct thread_info *th = find_thread_ptid (linux_target, lp->ptid);
|
||||
|
||||
if (th)
|
||||
if (del_thread)
|
||||
{
|
||||
if (print_thread_events)
|
||||
gdb_printf (_("[%s exited]\n"),
|
||||
target_pid_to_str (lp->ptid).c_str ());
|
||||
|
||||
delete_thread (th);
|
||||
thread_info *th = find_thread_ptid (linux_target, lp->ptid);
|
||||
if (th != nullptr)
|
||||
delete_thread (th);
|
||||
}
|
||||
|
||||
delete_lwp (lp->ptid);
|
||||
@@ -1273,6 +1295,63 @@ get_detach_signal (struct lwp_info *lp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If LP has a pending fork/vfork/clone status, return it. */
|
||||
|
||||
static gdb::optional<target_waitstatus>
|
||||
get_pending_child_status (lwp_info *lp)
|
||||
{
|
||||
/* Check in lwp_info::status. */
|
||||
if (WIFSTOPPED (lp->status) && linux_is_extended_waitstatus (lp->status))
|
||||
{
|
||||
int event = linux_ptrace_get_extended_event (lp->status);
|
||||
|
||||
if (event == PTRACE_EVENT_FORK
|
||||
|| event == PTRACE_EVENT_VFORK
|
||||
|| event == PTRACE_EVENT_CLONE)
|
||||
{
|
||||
unsigned long child_pid;
|
||||
int ret = ptrace (PTRACE_GETEVENTMSG, lp->ptid.lwp (), 0, &child_pid);
|
||||
if (ret == 0)
|
||||
{
|
||||
target_waitstatus ws;
|
||||
|
||||
if (event == PTRACE_EVENT_FORK)
|
||||
ws.set_forked (ptid_t (child_pid, child_pid));
|
||||
else if (event == PTRACE_EVENT_VFORK)
|
||||
ws.set_vforked (ptid_t (child_pid, child_pid));
|
||||
else if (event == PTRACE_EVENT_CLONE)
|
||||
ws.set_thread_cloned (ptid_t (lp->ptid.pid (), child_pid));
|
||||
else
|
||||
gdb_assert_not_reached ("unhandled");
|
||||
|
||||
return ws;
|
||||
}
|
||||
else
|
||||
{
|
||||
perror_warning_with_name (_("Failed to retrieve event msg"));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check in lwp_info::waitstatus. */
|
||||
if (is_new_child_status (lp->waitstatus.kind ()))
|
||||
return lp->waitstatus;
|
||||
|
||||
thread_info *tp = find_thread_ptid (linux_target, lp->ptid);
|
||||
|
||||
/* Check in thread_info::pending_waitstatus. */
|
||||
if (tp->has_pending_waitstatus ()
|
||||
&& is_new_child_status (tp->pending_waitstatus ().kind ()))
|
||||
return tp->pending_waitstatus ();
|
||||
|
||||
/* Check in thread_info::pending_follow. */
|
||||
if (is_new_child_status (tp->pending_follow.kind ()))
|
||||
return tp->pending_follow;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/* Detach from LP. If SIGNO_P is non-NULL, then it points to the
|
||||
signal number that should be passed to the LWP when detaching.
|
||||
Otherwise pass any pending signal the LWP may have, if any. */
|
||||
@@ -1283,54 +1362,12 @@ detach_one_lwp (struct lwp_info *lp, int *signo_p)
|
||||
int lwpid = lp->ptid.lwp ();
|
||||
int signo;
|
||||
|
||||
gdb_assert (lp->status == 0 || WIFSTOPPED (lp->status));
|
||||
/* If the lwp/thread we are about to detach has a pending fork/clone
|
||||
event, there is a process/thread GDB is attached to that the core
|
||||
of GDB doesn't know about. Detach from it. */
|
||||
|
||||
/* If the lwp/thread we are about to detach has a pending fork event,
|
||||
there is a process GDB is attached to that the core of GDB doesn't know
|
||||
about. Detach from it. */
|
||||
|
||||
/* Check in lwp_info::status. */
|
||||
if (WIFSTOPPED (lp->status) && linux_is_extended_waitstatus (lp->status))
|
||||
{
|
||||
int event = linux_ptrace_get_extended_event (lp->status);
|
||||
|
||||
if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
|
||||
{
|
||||
unsigned long child_pid;
|
||||
int ret = ptrace (PTRACE_GETEVENTMSG, lp->ptid.lwp (), 0, &child_pid);
|
||||
if (ret == 0)
|
||||
detach_one_pid (child_pid, 0);
|
||||
else
|
||||
perror_warning_with_name (_("Failed to detach fork child"));
|
||||
}
|
||||
}
|
||||
|
||||
/* Check in lwp_info::waitstatus. */
|
||||
if (lp->waitstatus.kind () == TARGET_WAITKIND_VFORKED
|
||||
|| lp->waitstatus.kind () == TARGET_WAITKIND_FORKED)
|
||||
detach_one_pid (lp->waitstatus.child_ptid ().pid (), 0);
|
||||
|
||||
|
||||
/* Check in thread_info::pending_waitstatus. */
|
||||
thread_info *tp = find_thread_ptid (linux_target, lp->ptid);
|
||||
if (tp->has_pending_waitstatus ())
|
||||
{
|
||||
const target_waitstatus &ws = tp->pending_waitstatus ();
|
||||
|
||||
if (ws.kind () == TARGET_WAITKIND_VFORKED
|
||||
|| ws.kind () == TARGET_WAITKIND_FORKED)
|
||||
detach_one_pid (ws.child_ptid ().pid (), 0);
|
||||
}
|
||||
|
||||
/* Check in thread_info::pending_follow. */
|
||||
if (tp->pending_follow.kind () == TARGET_WAITKIND_VFORKED
|
||||
|| tp->pending_follow.kind () == TARGET_WAITKIND_FORKED)
|
||||
detach_one_pid (tp->pending_follow.child_ptid ().pid (), 0);
|
||||
|
||||
if (lp->status != 0)
|
||||
linux_nat_debug_printf ("Pending %s for %s on detach.",
|
||||
strsignal (WSTOPSIG (lp->status)),
|
||||
lp->ptid.to_string ().c_str ());
|
||||
if (gdb::optional<target_waitstatus> ws = get_pending_child_status (lp))
|
||||
detach_one_pid (ws->child_ptid ().lwp (), 0);
|
||||
|
||||
/* If there is a pending SIGSTOP, get rid of it. */
|
||||
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? */
|
||||
gdb_assert (signo == GDB_SIGNAL_0);
|
||||
|
||||
linux_nat_debug_printf ("Short circuiting for status 0x%x",
|
||||
lp->status);
|
||||
linux_nat_debug_printf ("Short circuiting for status %s",
|
||||
pending_status_str (lp).c_str ());
|
||||
|
||||
if (target_can_async_p ())
|
||||
{
|
||||
@@ -1808,6 +1845,53 @@ linux_handle_syscall_trap (struct lwp_info *lp, int stopping)
|
||||
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
|
||||
event, we need to add the new LWP to our list (and not report the
|
||||
trap to higher layers). This function returns non-zero if the
|
||||
@@ -1848,11 +1932,9 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
|
||||
internal_error (_("wait returned unexpected status 0x%x"), status);
|
||||
}
|
||||
|
||||
ptid_t child_ptid (new_pid, new_pid);
|
||||
|
||||
if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
|
||||
{
|
||||
open_proc_mem_file (child_ptid);
|
||||
open_proc_mem_file (ptid_t (new_pid, new_pid));
|
||||
|
||||
/* The arch-specific native code may need to know about new
|
||||
forks even if those end up never mapped to an
|
||||
@@ -1889,66 +1971,18 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
|
||||
}
|
||||
|
||||
if (event == PTRACE_EVENT_FORK)
|
||||
ourstatus->set_forked (child_ptid);
|
||||
ourstatus->set_forked (ptid_t (new_pid, new_pid));
|
||||
else if (event == PTRACE_EVENT_VFORK)
|
||||
ourstatus->set_vforked (child_ptid);
|
||||
ourstatus->set_vforked (ptid_t (new_pid, new_pid));
|
||||
else if (event == PTRACE_EVENT_CLONE)
|
||||
{
|
||||
struct lwp_info *new_lp;
|
||||
|
||||
ourstatus->set_ignore ();
|
||||
|
||||
linux_nat_debug_printf
|
||||
("Got clone event from LWP %d, new child is LWP %ld", pid, new_pid);
|
||||
|
||||
new_lp = add_lwp (ptid_t (lp->ptid.pid (), new_pid));
|
||||
new_lp->stopped = 1;
|
||||
new_lp->resumed = 1;
|
||||
/* Save the status again, we'll use it in follow_clone. */
|
||||
add_to_pid_list (&stopped_pids, new_pid, status);
|
||||
|
||||
/* If the thread_db layer is active, let it record the user
|
||||
level thread id and status, and add the thread to GDB's
|
||||
list. */
|
||||
if (!thread_db_notice_clone (lp->ptid, new_lp->ptid))
|
||||
{
|
||||
/* The process is not using thread_db. Add the LWP to
|
||||
GDB's list. */
|
||||
add_thread (linux_target, new_lp->ptid);
|
||||
}
|
||||
|
||||
/* Even if we're stopping the thread for some reason
|
||||
internal to this module, from the perspective of infrun
|
||||
and the user/frontend, this new thread is running until
|
||||
it next reports a stop. */
|
||||
set_running (linux_target, new_lp->ptid, true);
|
||||
set_executing (linux_target, new_lp->ptid, true);
|
||||
|
||||
if (WSTOPSIG (status) != SIGSTOP)
|
||||
{
|
||||
/* This can happen if someone starts sending signals to
|
||||
the new thread before it gets a chance to run, which
|
||||
have a lower number than SIGSTOP (e.g. SIGUSR1).
|
||||
This is an unlikely case, and harder to handle for
|
||||
fork / vfork than for clone, so we do not try - but
|
||||
we handle it for clone events here. */
|
||||
|
||||
new_lp->signalled = 1;
|
||||
|
||||
/* We created NEW_LP so it cannot yet contain STATUS. */
|
||||
gdb_assert (new_lp->status == 0);
|
||||
|
||||
/* Save the wait status to report later. */
|
||||
linux_nat_debug_printf
|
||||
("waitpid of new LWP %ld, saving status %s",
|
||||
(long) new_lp->ptid.lwp (), status_to_str (status).c_str ());
|
||||
new_lp->status = status;
|
||||
}
|
||||
else if (report_thread_events)
|
||||
{
|
||||
new_lp->waitstatus.set_thread_created ();
|
||||
new_lp->status = status;
|
||||
}
|
||||
|
||||
return 1;
|
||||
ourstatus->set_thread_cloned (ptid_t (lp->ptid.pid (), new_pid));
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1973,6 +2007,21 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
|
||||
thread execs, it changes its tid to the tgid, and the old
|
||||
tgid thread might have not been resumed. */
|
||||
lp->resumed = 1;
|
||||
|
||||
/* All other LWPs are gone now. We'll have received a thread
|
||||
exit notification for all threads other the execing one.
|
||||
That one, if it wasn't the leader, just silently changes its
|
||||
tid to the tgid, and the previous leader vanishes. Since
|
||||
Linux 3.0, the former thread ID can be retrieved with
|
||||
PTRACE_GETEVENTMSG, but since we support older kernels, don't
|
||||
bother with it, and just walk the LWP list. Even with
|
||||
PTRACE_GETEVENTMSG, we'd still need to lookup the
|
||||
corresponding LWP object, and it would be an extra ptrace
|
||||
syscall, so this way may even be more efficient. */
|
||||
for (lwp_info *other_lp : all_lwps_safe ())
|
||||
if (other_lp != lp && other_lp->ptid.pid () == lp->ptid.pid ())
|
||||
exit_lwp (other_lp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2089,8 +2138,7 @@ wait_lwp (struct lwp_info *lp)
|
||||
/* Check if the thread has exited. */
|
||||
if (WIFEXITED (status) || WIFSIGNALED (status))
|
||||
{
|
||||
if (report_thread_events
|
||||
|| lp->ptid.pid () == lp->ptid.lwp ())
|
||||
if (report_exit_events_for (lp) || is_leader (lp))
|
||||
{
|
||||
linux_nat_debug_printf ("LWP %d exited.", lp->ptid.pid ());
|
||||
|
||||
@@ -2871,7 +2919,7 @@ linux_nat_filter_event (int lwpid, int status)
|
||||
/* Check if the thread has exited. */
|
||||
if (WIFEXITED (status) || WIFSIGNALED (status))
|
||||
{
|
||||
if (!report_thread_events && !is_leader (lp))
|
||||
if (!report_exit_events_for (lp) && !is_leader (lp))
|
||||
{
|
||||
linux_nat_debug_printf ("%s exited.",
|
||||
lp->ptid.to_string ().c_str ());
|
||||
@@ -3081,10 +3129,11 @@ check_zombie_leaders (void)
|
||||
}
|
||||
}
|
||||
|
||||
/* Convenience function that is called when the kernel reports an exit
|
||||
event. This decides whether to report the event to GDB as a
|
||||
process exit event, a thread exit event, or to suppress the
|
||||
event. */
|
||||
/* Convenience function that is called when we're about to return an
|
||||
event to the core. If the event is an exit or signalled event,
|
||||
then this decides whether to report it as process-wide event, as a
|
||||
thread exit event, or to suppress it. All other event kinds are
|
||||
passed through unmodified. */
|
||||
|
||||
static ptid_t
|
||||
filter_exit_event (struct lwp_info *event_child,
|
||||
@@ -3092,14 +3141,28 @@ filter_exit_event (struct lwp_info *event_child,
|
||||
{
|
||||
ptid_t ptid = event_child->ptid;
|
||||
|
||||
/* Note we must filter TARGET_WAITKIND_SIGNALLED as well, otherwise
|
||||
if a non-leader thread exits with a signal, we'd report it to the
|
||||
core which would interpret it as the whole-process exiting.
|
||||
There is no TARGET_WAITKIND_THREAD_SIGNALLED event kind. */
|
||||
if (ourstatus->kind () != TARGET_WAITKIND_EXITED
|
||||
&& ourstatus->kind () != TARGET_WAITKIND_SIGNALLED)
|
||||
return ptid;
|
||||
|
||||
if (!is_leader (event_child))
|
||||
{
|
||||
if (report_thread_events)
|
||||
ourstatus->set_thread_exited (0);
|
||||
if (report_exit_events_for (event_child))
|
||||
{
|
||||
ourstatus->set_thread_exited (0);
|
||||
/* Delete lwp, but not thread_info, infrun will need it to
|
||||
process the event. */
|
||||
exit_lwp (event_child, false);
|
||||
}
|
||||
else
|
||||
ourstatus->set_ignore ();
|
||||
|
||||
exit_lwp (event_child);
|
||||
{
|
||||
ourstatus->set_ignore ();
|
||||
exit_lwp (event_child);
|
||||
}
|
||||
}
|
||||
|
||||
return ptid;
|
||||
@@ -3137,7 +3200,7 @@ linux_nat_wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus,
|
||||
if (lp != NULL)
|
||||
{
|
||||
linux_nat_debug_printf ("Using pending wait status %s for %s.",
|
||||
status_to_str (lp->status).c_str (),
|
||||
pending_status_str (lp).c_str (),
|
||||
lp->ptid.to_string ().c_str ());
|
||||
}
|
||||
|
||||
@@ -3321,10 +3384,7 @@ linux_nat_wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus,
|
||||
else
|
||||
lp->core = linux_common_core_of_thread (lp->ptid);
|
||||
|
||||
if (ourstatus->kind () == TARGET_WAITKIND_EXITED)
|
||||
return filter_exit_event (lp, ourstatus);
|
||||
|
||||
return lp->ptid;
|
||||
return filter_exit_event (lp, ourstatus);
|
||||
}
|
||||
|
||||
/* Resume LWPs that are currently stopped without any pending status
|
||||
@@ -3508,59 +3568,55 @@ kill_wait_callback (struct lwp_info *lp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Kill the fork children of any threads of inferior INF that are
|
||||
stopped at a fork event. */
|
||||
/* Kill the fork/clone child of LP if it has an unfollowed child. */
|
||||
|
||||
static void
|
||||
kill_unfollowed_fork_children (struct inferior *inf)
|
||||
static int
|
||||
kill_unfollowed_child_callback (lwp_info *lp)
|
||||
{
|
||||
for (thread_info *thread : inf->non_exited_threads ())
|
||||
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
|
||||
|| ws->kind () == TARGET_WAITKIND_VFORKED)
|
||||
{
|
||||
ptid_t child_ptid = ws->child_ptid ();
|
||||
int child_pid = child_ptid.pid ();
|
||||
int child_lwp = child_ptid.lwp ();
|
||||
kill_one_lwp (child_lwp);
|
||||
kill_wait_one_lwp (child_lwp);
|
||||
|
||||
kill_one_lwp (child_lwp);
|
||||
kill_wait_one_lwp (child_lwp);
|
||||
|
||||
/* Let the arch-specific native code know this process is
|
||||
gone. */
|
||||
linux_target->low_forget_process (child_pid);
|
||||
}
|
||||
/* Let the arch-specific native code know this process is
|
||||
gone. */
|
||||
if (ws->kind () != TARGET_WAITKIND_THREAD_CLONED)
|
||||
linux_target->low_forget_process (child_pid);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
linux_nat_target::kill ()
|
||||
{
|
||||
/* If we're stopped while forking and we haven't followed yet,
|
||||
kill the other task. We need to do this first because the
|
||||
ptid_t pid_ptid (inferior_ptid.pid ());
|
||||
|
||||
/* If we're stopped while forking/cloning and we haven't followed
|
||||
yet, kill the child task. We need to do this first because the
|
||||
parent will be sleeping if this is a vfork. */
|
||||
kill_unfollowed_fork_children (current_inferior ());
|
||||
iterate_over_lwps (pid_ptid, kill_unfollowed_child_callback);
|
||||
|
||||
if (forks_exist_p ())
|
||||
linux_fork_killall ();
|
||||
else
|
||||
{
|
||||
ptid_t ptid = ptid_t (inferior_ptid.pid ());
|
||||
|
||||
/* Stop all threads before killing them, since ptrace requires
|
||||
that the thread is stopped to successfully PTRACE_KILL. */
|
||||
iterate_over_lwps (ptid, stop_callback);
|
||||
iterate_over_lwps (pid_ptid, stop_callback);
|
||||
/* ... and wait until all of them have reported back that
|
||||
they're no longer running. */
|
||||
iterate_over_lwps (ptid, stop_wait_callback);
|
||||
iterate_over_lwps (pid_ptid, stop_wait_callback);
|
||||
|
||||
/* Kill all LWP's ... */
|
||||
iterate_over_lwps (ptid, kill_callback);
|
||||
iterate_over_lwps (pid_ptid, kill_callback);
|
||||
|
||||
/* ... and wait until we've flushed all events. */
|
||||
iterate_over_lwps (ptid, kill_wait_callback);
|
||||
iterate_over_lwps (pid_ptid, kill_wait_callback);
|
||||
}
|
||||
|
||||
target_mourn_inferior (inferior_ptid);
|
||||
@@ -4432,6 +4488,14 @@ linux_nat_target::thread_events (int enable)
|
||||
report_thread_events = enable;
|
||||
}
|
||||
|
||||
bool
|
||||
linux_nat_target::supports_set_thread_options (gdb_thread_options options)
|
||||
{
|
||||
constexpr gdb_thread_options supported_options
|
||||
= GDB_THREAD_OPTION_CLONE | GDB_THREAD_OPTION_EXIT;
|
||||
return ((options & supported_options) == options);
|
||||
}
|
||||
|
||||
linux_nat_target::linux_nat_target ()
|
||||
{
|
||||
/* We don't change the stratum; this target will sit at
|
||||
|
||||
@@ -82,6 +82,8 @@ public:
|
||||
|
||||
void thread_events (int) override;
|
||||
|
||||
bool supports_set_thread_options (gdb_thread_options options) override;
|
||||
|
||||
bool can_async_p () override;
|
||||
|
||||
bool supports_non_stop () override;
|
||||
@@ -129,6 +131,8 @@ public:
|
||||
|
||||
void follow_fork (inferior *, ptid_t, target_waitkind, bool, bool) override;
|
||||
|
||||
void follow_clone (ptid_t) override;
|
||||
|
||||
std::vector<static_tracepoint_marker>
|
||||
static_tracepoint_markers_by_strid (const char *id) override;
|
||||
|
||||
|
||||
@@ -2621,13 +2621,14 @@ linux_displaced_step_prepare (gdbarch *arch, thread_info *thread,
|
||||
/* See linux-tdep.h. */
|
||||
|
||||
displaced_step_finish_status
|
||||
linux_displaced_step_finish (gdbarch *arch, thread_info *thread, gdb_signal sig)
|
||||
linux_displaced_step_finish (gdbarch *arch, thread_info *thread,
|
||||
const target_waitstatus &status)
|
||||
{
|
||||
linux_info *per_inferior = get_linux_inferior_data (thread->inf);
|
||||
|
||||
gdb_assert (per_inferior->disp_step_bufs.has_value ());
|
||||
|
||||
return per_inferior->disp_step_bufs->finish (arch, thread, sig);
|
||||
return per_inferior->disp_step_bufs->finish (arch, thread, status);
|
||||
}
|
||||
|
||||
/* See linux-tdep.h. */
|
||||
|
||||
@@ -72,7 +72,7 @@ extern displaced_step_prepare_status linux_displaced_step_prepare
|
||||
/* Implementation of gdbarch_displaced_step_finish. */
|
||||
|
||||
extern displaced_step_finish_status linux_displaced_step_finish
|
||||
(gdbarch *arch, thread_info *thread, gdb_signal sig);
|
||||
(gdbarch *arch, thread_info *thread, const target_waitstatus &status);
|
||||
|
||||
/* Implementation of gdbarch_displaced_step_copy_insn_closure_by_addr. */
|
||||
|
||||
|
||||
@@ -68,7 +68,9 @@ static void mi_on_normal_stop (struct bpstat *bs, int print_frame);
|
||||
static void mi_on_no_history (void);
|
||||
|
||||
static void mi_new_thread (struct thread_info *t);
|
||||
static void mi_thread_exit (struct thread_info *t, int silent);
|
||||
static void mi_thread_exit (thread_info *t,
|
||||
gdb::optional<ULONGEST> exit_code,
|
||||
bool silent);
|
||||
static void mi_record_changed (struct inferior*, int, const char *,
|
||||
const char *);
|
||||
static void mi_inferior_added (struct inferior *inf);
|
||||
@@ -351,8 +353,10 @@ mi_new_thread (struct thread_info *t)
|
||||
}
|
||||
}
|
||||
|
||||
/* Observer for the thread_exit notification. */
|
||||
|
||||
static void
|
||||
mi_thread_exit (struct thread_info *t, int silent)
|
||||
mi_thread_exit (thread_info *t, gdb::optional<ULONGEST> exit_code, bool silent)
|
||||
{
|
||||
SWITCH_THRU_ALL_UIS ()
|
||||
{
|
||||
|
||||
@@ -625,11 +625,6 @@ nbsd_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
|
||||
{
|
||||
/* NetBSD does not store an LWP exit status. */
|
||||
ourstatus->set_thread_exited (0);
|
||||
|
||||
if (print_thread_events)
|
||||
gdb_printf (_("[%s exited]\n"),
|
||||
target_pid_to_str (wptid).c_str ());
|
||||
delete_thread (thr);
|
||||
}
|
||||
|
||||
/* The GDB core expects that the rest of the threads are running. */
|
||||
|
||||
@@ -658,7 +658,7 @@ nto_procfs_target::files_info ()
|
||||
|
||||
gdb_printf ("\tUsing the running image of %s %s via %s.\n",
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
@@ -126,10 +126,13 @@ extern observable<struct objfile */* objfile */> free_objfile;
|
||||
/* The thread specified by T has been created. */
|
||||
extern observable<struct thread_info */* t */> new_thread;
|
||||
|
||||
/* The thread specified by T has exited. The SILENT argument
|
||||
indicates that gdb is removing the thread from its tables without
|
||||
wanting to notify the user about it. */
|
||||
extern observable<struct thread_info */* t */, int /* silent */> thread_exit;
|
||||
/* The thread specified by T has exited. EXIT_CODE is the thread's
|
||||
exit code, if available. The SILENT argument indicates that GDB is
|
||||
removing the thread from its tables without wanting to notify the
|
||||
CLI about it. */
|
||||
extern observable<thread_info */* t */,
|
||||
gdb::optional<ULONGEST> /* exit_code */,
|
||||
bool /* silent */> thread_exit;
|
||||
|
||||
/* An explicit stop request was issued to PTID. If PTID equals
|
||||
minus_one_ptid, the request applied to all threads. If
|
||||
|
||||
@@ -2115,9 +2115,6 @@ wait_again:
|
||||
case PR_SYSENTRY:
|
||||
if (what == SYS_lwp_exit)
|
||||
{
|
||||
if (print_thread_events)
|
||||
gdb_printf (_("[%s exited]\n"),
|
||||
target_pid_to_str (retval).c_str ());
|
||||
delete_thread (find_thread_ptid (this, retval));
|
||||
target_continue_no_signal (ptid);
|
||||
goto wait_again;
|
||||
@@ -2222,9 +2219,6 @@ wait_again:
|
||||
}
|
||||
else if (what == SYS_lwp_exit)
|
||||
{
|
||||
if (print_thread_events)
|
||||
gdb_printf (_("[%s exited]\n"),
|
||||
target_pid_to_str (retval).c_str ());
|
||||
delete_thread (find_thread_ptid (this, retval));
|
||||
status->set_spurious ();
|
||||
return retval;
|
||||
@@ -2533,7 +2527,7 @@ procfs_target::files_info ()
|
||||
|
||||
gdb_printf (_("\tUsing the running image of %s %s via /proc.\n"),
|
||||
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
|
||||
|
||||
@@ -360,7 +360,9 @@ add_thread_object (struct thread_info *tp)
|
||||
}
|
||||
|
||||
static void
|
||||
delete_thread_object (struct thread_info *tp, int ignore)
|
||||
delete_thread_object (thread_info *tp,
|
||||
gdb::optional<ULONGEST> /* exit_code */,
|
||||
bool /* silent */)
|
||||
{
|
||||
if (!gdb_python_initialized)
|
||||
return;
|
||||
|
||||
264
gdb/remote.c
264
gdb/remote.c
@@ -314,6 +314,10 @@ public: /* data */
|
||||
the target know about program signals list changes. */
|
||||
char *last_program_signals_packet = nullptr;
|
||||
|
||||
/* Similarly, the last QThreadEvents state we sent to the
|
||||
target. */
|
||||
bool last_thread_events = false;
|
||||
|
||||
gdb_signal last_sent_signal = GDB_SIGNAL_0;
|
||||
|
||||
bool last_sent_step = false;
|
||||
@@ -381,6 +385,10 @@ public: /* data */
|
||||
this can go away. */
|
||||
int wait_forever_enabled_p = 1;
|
||||
|
||||
/* The set of thread options the target reported it supports, via
|
||||
qSupported. */
|
||||
gdb_thread_options supported_thread_options = 0;
|
||||
|
||||
private:
|
||||
/* Mapping of remote protocol data for each gdbarch. Usually there
|
||||
is only one entry here, though we may see more with stubs that
|
||||
@@ -417,6 +425,8 @@ public:
|
||||
void detach (inferior *, int) override;
|
||||
void disconnect (const char *, int) override;
|
||||
|
||||
void commit_requested_thread_options ();
|
||||
|
||||
void commit_resumed () override;
|
||||
void resume (ptid_t, int, enum gdb_signal) override;
|
||||
ptid_t wait (ptid_t, struct target_waitstatus *, target_wait_flags) override;
|
||||
@@ -543,6 +553,8 @@ public:
|
||||
|
||||
void thread_events (int) override;
|
||||
|
||||
bool supports_set_thread_options (gdb_thread_options) override;
|
||||
|
||||
int can_do_single_step () override;
|
||||
|
||||
void terminal_inferior () override;
|
||||
@@ -672,6 +684,7 @@ public:
|
||||
const struct btrace_config *btrace_conf (const struct btrace_target_info *) override;
|
||||
bool augmented_libraries_svr4_read () override;
|
||||
void follow_fork (inferior *, ptid_t, target_waitkind, bool, bool) override;
|
||||
void follow_clone (ptid_t child_ptid) override;
|
||||
void follow_exec (inferior *, ptid_t, const char *) override;
|
||||
int insert_fork_catchpoint (int) override;
|
||||
int remove_fork_catchpoint (int) override;
|
||||
@@ -765,7 +778,7 @@ public: /* Remote specific methods. */
|
||||
|
||||
void remote_btrace_maybe_reopen ();
|
||||
|
||||
void remove_new_fork_children (threads_listing_context *context);
|
||||
void remove_new_children (threads_listing_context *context);
|
||||
void kill_new_fork_children (inferior *inf);
|
||||
void discard_pending_stop_replies (struct inferior *inf);
|
||||
int stop_reply_queue_length ();
|
||||
@@ -832,6 +845,9 @@ public: /* Remote specific methods. */
|
||||
|
||||
void remote_packet_size (const protocol_feature *feature,
|
||||
packet_support support, const char *value);
|
||||
void remote_supported_thread_options (const protocol_feature *feature,
|
||||
enum packet_support support,
|
||||
const char *value);
|
||||
|
||||
void remote_serial_quit_handler ();
|
||||
|
||||
@@ -2164,6 +2180,9 @@ enum {
|
||||
/* Support for the QThreadEvents packet. */
|
||||
PACKET_QThreadEvents,
|
||||
|
||||
/* Support for the QThreadOptions packet. */
|
||||
PACKET_QThreadOptions,
|
||||
|
||||
/* Support for multi-process extensions. */
|
||||
PACKET_multiprocess_feature,
|
||||
|
||||
@@ -2579,9 +2598,10 @@ remote_target::remote_add_thread (ptid_t ptid, bool running, bool executing,
|
||||
else
|
||||
thread = add_thread (this, ptid);
|
||||
|
||||
/* We start by assuming threads are resumed. That state then gets updated
|
||||
when we process a matching stop reply. */
|
||||
get_remote_thread_info (thread)->set_resumed ();
|
||||
/* We start by assuming threads are resumed. That state then gets
|
||||
updated when we process a matching stop reply. */
|
||||
if (executing)
|
||||
get_remote_thread_info (thread)->set_resumed ();
|
||||
|
||||
set_executing (this, ptid, executing);
|
||||
set_running (this, ptid, running);
|
||||
@@ -3978,15 +3998,25 @@ remote_target::update_thread_list ()
|
||||
if (has_single_non_exited_thread (tp->inf))
|
||||
continue;
|
||||
|
||||
/* Do not remove the thread if we've requested to be
|
||||
notified of its exit. For example, the thread may be
|
||||
displaced stepping, infrun will need to handle the
|
||||
exit event, and displaced stepping info is recorded
|
||||
in the thread object. If we deleted the thread now,
|
||||
we'd lose that info. */
|
||||
if ((tp->thread_options () & GDB_THREAD_OPTION_EXIT) != 0)
|
||||
continue;
|
||||
|
||||
/* Not found. */
|
||||
delete_thread (tp);
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove any unreported fork child threads from CONTEXT so
|
||||
that we don't interfere with follow fork, which is where
|
||||
creation of such threads is handled. */
|
||||
remove_new_fork_children (&context);
|
||||
/* Remove any unreported fork/vfork/clone child threads from
|
||||
CONTEXT so that we don't interfere with follow
|
||||
fork/vfork/clone, which is where creation of such threads is
|
||||
handled. */
|
||||
remove_new_children (&context);
|
||||
|
||||
/* And now add threads we don't know about yet to our list. */
|
||||
for (thread_item &item : context.items)
|
||||
@@ -4940,6 +4970,8 @@ remote_target::start_remote_1 (int from_tty, int extended_p)
|
||||
}
|
||||
else
|
||||
switch_to_thread (find_thread_ptid (this, curr_thread));
|
||||
|
||||
get_remote_thread_info (inferior_thread ())->set_resumed ();
|
||||
}
|
||||
|
||||
/* init_wait_for_inferior should be called before get_offsets in order
|
||||
@@ -5281,7 +5313,8 @@ remote_supported_packet (remote_target *remote,
|
||||
|
||||
void
|
||||
remote_target::remote_packet_size (const protocol_feature *feature,
|
||||
enum packet_support support, const char *value)
|
||||
enum packet_support support,
|
||||
const char *value)
|
||||
{
|
||||
struct remote_state *rs = get_remote_state ();
|
||||
|
||||
@@ -5318,6 +5351,49 @@ remote_packet_size (remote_target *remote, const protocol_feature *feature,
|
||||
remote->remote_packet_size (feature, support, value);
|
||||
}
|
||||
|
||||
void
|
||||
remote_target::remote_supported_thread_options (const protocol_feature *feature,
|
||||
enum packet_support support,
|
||||
const char *value)
|
||||
{
|
||||
struct remote_state *rs = get_remote_state ();
|
||||
|
||||
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[] = {
|
||||
{ "PacketSize", PACKET_DISABLE, remote_packet_size, -1 },
|
||||
{ "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 },
|
||||
{ "vContSupported", PACKET_DISABLE, remote_supported_packet, PACKET_vContSupported },
|
||||
{ "QThreadEvents", PACKET_DISABLE, remote_supported_packet, PACKET_QThreadEvents },
|
||||
{ "QThreadOptions", PACKET_DISABLE, remote_supported_thread_options,
|
||||
PACKET_QThreadOptions },
|
||||
{ "no-resumed", PACKET_DISABLE, remote_supported_packet, PACKET_no_resumed },
|
||||
{ "memory-tagging", PACKET_DISABLE, remote_supported_packet,
|
||||
PACKET_memory_tagging_feature },
|
||||
@@ -5514,6 +5592,9 @@ remote_target::remote_query_supported ()
|
||||
if (packet_set_cmd_state (PACKET_QThreadEvents) != AUTO_BOOLEAN_FALSE)
|
||||
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)
|
||||
remote_query_supported_append (&q, "no-resumed+");
|
||||
|
||||
@@ -5893,16 +5974,25 @@ is_fork_status (target_waitkind kind)
|
||||
|| kind == TARGET_WAITKIND_VFORKED);
|
||||
}
|
||||
|
||||
/* Return THREAD's pending status if it is a pending fork parent, else
|
||||
return nullptr. */
|
||||
/* Return a reference to the field where a pending child status, if
|
||||
there's one, is recorded. If there's no child event pending, the
|
||||
returned waitstatus has TARGET_WAITKIND_IGNORE kind. */
|
||||
|
||||
static const target_waitstatus &
|
||||
thread_pending_status (struct thread_info *thread)
|
||||
{
|
||||
return (thread->has_pending_waitstatus ()
|
||||
? thread->pending_waitstatus ()
|
||||
: thread->pending_follow);
|
||||
}
|
||||
|
||||
/* Return THREAD's pending status if it is a pending fork/vfork (but
|
||||
not clone) parent, else return nullptr. */
|
||||
|
||||
static const target_waitstatus *
|
||||
thread_pending_fork_status (struct thread_info *thread)
|
||||
{
|
||||
const target_waitstatus &ws
|
||||
= (thread->has_pending_waitstatus ()
|
||||
? thread->pending_waitstatus ()
|
||||
: thread->pending_follow);
|
||||
const target_waitstatus &ws = thread_pending_status (thread);
|
||||
|
||||
if (!is_fork_status (ws.kind ()))
|
||||
return nullptr;
|
||||
@@ -5910,6 +6000,20 @@ thread_pending_fork_status (struct thread_info *thread)
|
||||
return &ws;
|
||||
}
|
||||
|
||||
/* Return THREAD's pending status if is is a pending fork/vfork/clone
|
||||
event, else return nullptr. */
|
||||
|
||||
static const target_waitstatus *
|
||||
thread_pending_child_status (thread_info *thread)
|
||||
{
|
||||
const target_waitstatus &ws = thread_pending_status (thread);
|
||||
|
||||
if (!is_new_child_status (ws.kind ()))
|
||||
return nullptr;
|
||||
|
||||
return &ws;
|
||||
}
|
||||
|
||||
/* Detach the specified process. */
|
||||
|
||||
void
|
||||
@@ -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
|
||||
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;
|
||||
}
|
||||
|
||||
commit_requested_thread_options ();
|
||||
|
||||
/* In all-stop, we can't mark REMOTE_ASYNC_GET_PENDING_EVENTS_TOKEN
|
||||
(explained in remote-notif.c:handle_notification) so
|
||||
remote_notif_process is not called. We need find a place where
|
||||
@@ -6723,6 +6835,8 @@ remote_target::commit_resumed ()
|
||||
if (!target_is_non_stop_p () || ::execution_direction == EXEC_REVERSE)
|
||||
return;
|
||||
|
||||
commit_requested_thread_options ();
|
||||
|
||||
/* Try to send wildcard actions ("vCont;c" or "vCont;c:pPID.-1")
|
||||
instead of resuming all threads of each process individually.
|
||||
However, if any thread of a process must remain halted, we can't
|
||||
@@ -6807,10 +6921,10 @@ remote_target::commit_resumed ()
|
||||
if (priv->get_resume_state () == resume_state::RESUMED_PENDING_VCONT)
|
||||
any_pending_vcont_resume = true;
|
||||
|
||||
/* If a thread is the parent of an unfollowed fork, then we
|
||||
can't do a global wildcard, as that would resume the fork
|
||||
child. */
|
||||
if (thread_pending_fork_status (tp) != nullptr)
|
||||
/* If a thread is the parent of an unfollowed fork/vfork/clone,
|
||||
then we can't do a global wildcard, as that would resume the
|
||||
pending child. */
|
||||
if (thread_pending_child_status (tp) != nullptr)
|
||||
may_global_wildcard_vcont = false;
|
||||
}
|
||||
|
||||
@@ -7276,22 +7390,22 @@ struct notif_client notif_client_stop =
|
||||
REMOTE_NOTIF_STOP,
|
||||
};
|
||||
|
||||
/* If CONTEXT contains any fork child threads that have not been
|
||||
reported yet, remove them from the CONTEXT list. If such a
|
||||
thread exists it is because we are stopped at a fork catchpoint
|
||||
and have not yet called follow_fork, which will set up the
|
||||
host-side data structures for the new process. */
|
||||
/* If CONTEXT contains any fork/vfork/clone child threads that have
|
||||
not been reported yet, remove them from the CONTEXT list. If such
|
||||
a thread exists it is because we are stopped at a fork/vfork/clone
|
||||
catchpoint and have not yet called follow_fork/follow_clone, which
|
||||
will set up the host-side data structures for the new child. */
|
||||
|
||||
void
|
||||
remote_target::remove_new_fork_children (threads_listing_context *context)
|
||||
remote_target::remove_new_children (threads_listing_context *context)
|
||||
{
|
||||
struct notif_client *notif = ¬if_client_stop;
|
||||
|
||||
/* For any threads stopped at a fork event, remove the corresponding
|
||||
fork child threads from the CONTEXT list. */
|
||||
/* For any threads stopped at a (v)fork/clone event, remove the
|
||||
corresponding child threads from the CONTEXT list. */
|
||||
for (thread_info *thread : all_non_exited_threads (this))
|
||||
{
|
||||
const target_waitstatus *ws = thread_pending_fork_status (thread);
|
||||
const target_waitstatus *ws = thread_pending_child_status (thread);
|
||||
|
||||
if (ws == nullptr)
|
||||
continue;
|
||||
@@ -7299,13 +7413,12 @@ remote_target::remove_new_fork_children (threads_listing_context *context)
|
||||
context->remove_thread (ws->child_ptid ());
|
||||
}
|
||||
|
||||
/* Check for any pending fork events (not reported or processed yet)
|
||||
in process PID and remove those fork child threads from the
|
||||
CONTEXT list as well. */
|
||||
/* Check for any pending (v)fork/clone events (not reported or
|
||||
processed yet) in process PID and remove those child threads from
|
||||
the CONTEXT list as well. */
|
||||
remote_notif_get_pending_events (notif);
|
||||
for (auto &event : get_remote_state ()->stop_reply_queue)
|
||||
if (event->ws.kind () == TARGET_WAITKIND_FORKED
|
||||
|| event->ws.kind () == TARGET_WAITKIND_VFORKED)
|
||||
if (is_new_child_status (event->ws.kind ()))
|
||||
context->remove_thread (event->ws.child_ptid ());
|
||||
else if (event->ws.kind () == TARGET_WAITKIND_THREAD_EXITED)
|
||||
context->remove_thread (event->ptid);
|
||||
@@ -7634,6 +7747,8 @@ Packet: '%s'\n"),
|
||||
event->ws.set_forked (read_ptid (++p1, &p));
|
||||
else if (strprefix (p, p1, "vfork"))
|
||||
event->ws.set_vforked (read_ptid (++p1, &p));
|
||||
else if (strprefix (p, p1, "clone"))
|
||||
event->ws.set_thread_cloned (read_ptid (++p1, &p));
|
||||
else if (strprefix (p, p1, "vforkdone"))
|
||||
{
|
||||
event->ws.set_vfork_done ();
|
||||
@@ -8066,7 +8181,8 @@ remote_target::process_stop_reply (struct stop_reply *stop_reply,
|
||||
&& status->kind () != TARGET_WAITKIND_NO_RESUMED)
|
||||
{
|
||||
/* Expedited registers. */
|
||||
if (!stop_reply->regcache.empty ())
|
||||
if (status->kind () != TARGET_WAITKIND_THREAD_EXITED
|
||||
&& !stop_reply->regcache.empty ())
|
||||
{
|
||||
struct regcache *regcache
|
||||
= get_thread_arch_regcache (this, ptid, stop_reply->arch);
|
||||
@@ -8252,7 +8368,7 @@ remote_target::wait_as (ptid_t ptid, target_waitstatus *status,
|
||||
again. Keep waiting for events. */
|
||||
rs->waiting_for_stop_reply = 1;
|
||||
break;
|
||||
case 'N': case 'T': case 'S': case 'X': case 'W':
|
||||
case 'N': case 'T': case 'S': case 'X': case 'W': case 'w':
|
||||
{
|
||||
/* There is a stop reply to handle. */
|
||||
rs->waiting_for_stop_reply = 0;
|
||||
@@ -14472,6 +14588,9 @@ remote_target::thread_events (int enable)
|
||||
if (packet_support (PACKET_QThreadEvents) == PACKET_DISABLE)
|
||||
return;
|
||||
|
||||
if (rs->last_thread_events == enable)
|
||||
return;
|
||||
|
||||
xsnprintf (rs->buf.data (), size, "QThreadEvents:%x", enable ? 1 : 0);
|
||||
putpkt (rs->buf);
|
||||
getpkt (&rs->buf, 0);
|
||||
@@ -14482,6 +14601,7 @@ remote_target::thread_events (int enable)
|
||||
case PACKET_OK:
|
||||
if (strcmp (rs->buf.data (), "OK") != 0)
|
||||
error (_("Remote refused setting thread events: %s"), rs->buf.data ());
|
||||
rs->last_thread_events = enable;
|
||||
break;
|
||||
case PACKET_ERROR:
|
||||
warning (_("Remote failure reply: %s"), rs->buf.data ());
|
||||
@@ -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
|
||||
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],
|
||||
"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],
|
||||
"N stop reply", "no-resumed-stop-reply", 0);
|
||||
|
||||
|
||||
@@ -1088,13 +1088,13 @@ ppc_displaced_step_prepare (gdbarch *arch, thread_info *thread,
|
||||
|
||||
static displaced_step_finish_status
|
||||
ppc_displaced_step_finish (gdbarch *arch, thread_info *thread,
|
||||
gdb_signal sig)
|
||||
const target_waitstatus &status)
|
||||
{
|
||||
ppc_inferior_data *per_inferior = get_ppc_per_inferior (thread->inf);
|
||||
|
||||
gdb_assert (per_inferior->disp_step_buf.has_value ());
|
||||
|
||||
return per_inferior->disp_step_buf->finish (arch, thread, sig);
|
||||
return per_inferior->disp_step_buf->finish (arch, thread, status);
|
||||
}
|
||||
|
||||
/* Implementation of gdbarch_displaced_step_restore_all_in_ptid. */
|
||||
|
||||
@@ -176,6 +176,8 @@
|
||||
target_debug_do_print (X.get ())
|
||||
#define target_debug_print_target_waitkind(X) \
|
||||
target_debug_do_print (pulongest (X))
|
||||
#define target_debug_print_gdb_thread_options(X) \
|
||||
target_debug_do_print (to_string (X).c_str ())
|
||||
|
||||
static void
|
||||
target_debug_print_struct_target_waitstatus_p (struct target_waitstatus *status)
|
||||
|
||||
@@ -76,6 +76,7 @@ struct dummy_target : public target_ops
|
||||
int insert_vfork_catchpoint (int arg0) override;
|
||||
int remove_vfork_catchpoint (int arg0) override;
|
||||
void follow_fork (inferior *arg0, ptid_t arg1, target_waitkind arg2, bool arg3, bool arg4) override;
|
||||
void follow_clone (ptid_t arg0) override;
|
||||
int insert_exec_catchpoint (int arg0) override;
|
||||
int remove_exec_catchpoint (int arg0) override;
|
||||
void follow_exec (inferior *arg0, ptid_t arg1, const char *arg2) override;
|
||||
@@ -105,6 +106,7 @@ struct dummy_target : public target_ops
|
||||
int async_wait_fd () override;
|
||||
bool has_pending_events () override;
|
||||
void thread_events (int arg0) override;
|
||||
bool supports_set_thread_options (gdb_thread_options arg0) override;
|
||||
bool supports_non_stop () override;
|
||||
bool always_non_stop_p () override;
|
||||
int find_memory_regions (find_memory_region_ftype arg0, void *arg1) override;
|
||||
@@ -250,6 +252,7 @@ struct debug_target : public target_ops
|
||||
int insert_vfork_catchpoint (int arg0) override;
|
||||
int remove_vfork_catchpoint (int arg0) override;
|
||||
void follow_fork (inferior *arg0, ptid_t arg1, target_waitkind arg2, bool arg3, bool arg4) override;
|
||||
void follow_clone (ptid_t arg0) override;
|
||||
int insert_exec_catchpoint (int arg0) override;
|
||||
int remove_exec_catchpoint (int arg0) override;
|
||||
void follow_exec (inferior *arg0, ptid_t arg1, const char *arg2) override;
|
||||
@@ -279,6 +282,7 @@ struct debug_target : public target_ops
|
||||
int async_wait_fd () override;
|
||||
bool has_pending_events () override;
|
||||
void thread_events (int arg0) override;
|
||||
bool supports_set_thread_options (gdb_thread_options arg0) override;
|
||||
bool supports_non_stop () override;
|
||||
bool always_non_stop_p () override;
|
||||
int find_memory_regions (find_memory_region_ftype arg0, void *arg1) override;
|
||||
@@ -1545,6 +1549,28 @@ debug_target::follow_fork (inferior *arg0, ptid_t arg1, target_waitkind arg2, bo
|
||||
gdb_puts (")\n", gdb_stdlog);
|
||||
}
|
||||
|
||||
void
|
||||
target_ops::follow_clone (ptid_t arg0)
|
||||
{
|
||||
this->beneath ()->follow_clone (arg0);
|
||||
}
|
||||
|
||||
void
|
||||
dummy_target::follow_clone (ptid_t arg0)
|
||||
{
|
||||
default_follow_clone (this, arg0);
|
||||
}
|
||||
|
||||
void
|
||||
debug_target::follow_clone (ptid_t arg0)
|
||||
{
|
||||
gdb_printf (gdb_stdlog, "-> %s->follow_clone (...)\n", this->beneath ()->shortname ());
|
||||
this->beneath ()->follow_clone (arg0);
|
||||
gdb_printf (gdb_stdlog, "<- %s->follow_clone (", this->beneath ()->shortname ());
|
||||
target_debug_print_ptid_t (arg0);
|
||||
gdb_puts (")\n", gdb_stdlog);
|
||||
}
|
||||
|
||||
int
|
||||
target_ops::insert_exec_catchpoint (int arg0)
|
||||
{
|
||||
@@ -2248,6 +2274,32 @@ debug_target::thread_events (int arg0)
|
||||
gdb_puts (")\n", gdb_stdlog);
|
||||
}
|
||||
|
||||
bool
|
||||
target_ops::supports_set_thread_options (gdb_thread_options arg0)
|
||||
{
|
||||
return this->beneath ()->supports_set_thread_options (arg0);
|
||||
}
|
||||
|
||||
bool
|
||||
dummy_target::supports_set_thread_options (gdb_thread_options arg0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
debug_target::supports_set_thread_options (gdb_thread_options arg0)
|
||||
{
|
||||
bool result;
|
||||
gdb_printf (gdb_stdlog, "-> %s->supports_set_thread_options (...)\n", this->beneath ()->shortname ());
|
||||
result = this->beneath ()->supports_set_thread_options (arg0);
|
||||
gdb_printf (gdb_stdlog, "<- %s->supports_set_thread_options (", this->beneath ()->shortname ());
|
||||
target_debug_print_gdb_thread_options (arg0);
|
||||
gdb_puts (") = ", gdb_stdlog);
|
||||
target_debug_print_bool (result);
|
||||
gdb_puts ("\n", gdb_stdlog);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool
|
||||
target_ops::supports_non_stop ()
|
||||
{
|
||||
|
||||
21
gdb/target.c
21
gdb/target.c
@@ -2502,6 +2502,8 @@ target_pre_inferior (int from_tty)
|
||||
|
||||
current_inferior ()->highest_thread_num = 0;
|
||||
|
||||
update_previous_thread ();
|
||||
|
||||
agent_capability_invalidate ();
|
||||
}
|
||||
|
||||
@@ -2530,6 +2532,9 @@ target_preopen (int from_tty)
|
||||
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
|
||||
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
|
||||
@@ -2727,6 +2732,13 @@ default_follow_fork (struct target_ops *self, inferior *child_inf,
|
||||
internal_error (_("could not find a target to follow fork"));
|
||||
}
|
||||
|
||||
static void
|
||||
default_follow_clone (struct target_ops *self, ptid_t child_ptid)
|
||||
{
|
||||
/* Some target returned a clone event, but did not know how to follow it. */
|
||||
internal_error (_("could not find a target to follow clone"));
|
||||
}
|
||||
|
||||
/* See target.h. */
|
||||
|
||||
void
|
||||
@@ -4377,6 +4389,15 @@ target_thread_events (int enable)
|
||||
current_inferior ()->top_target ()->thread_events (enable);
|
||||
}
|
||||
|
||||
/* See target.h. */
|
||||
|
||||
bool
|
||||
target_supports_set_thread_options (gdb_thread_options options)
|
||||
{
|
||||
inferior *inf = current_inferior ();
|
||||
return inf->top_target ()->supports_set_thread_options (options);
|
||||
}
|
||||
|
||||
/* Controls if targets can report that they can/are async. This is
|
||||
just for maintainers to use when debugging gdb. */
|
||||
bool target_async_permitted = true;
|
||||
|
||||
10
gdb/target.h
10
gdb/target.h
@@ -637,6 +637,8 @@ struct target_ops
|
||||
TARGET_DEFAULT_RETURN (1);
|
||||
virtual void follow_fork (inferior *, ptid_t, target_waitkind, bool, bool)
|
||||
TARGET_DEFAULT_FUNC (default_follow_fork);
|
||||
virtual void follow_clone (ptid_t)
|
||||
TARGET_DEFAULT_FUNC (default_follow_clone);
|
||||
virtual int insert_exec_catchpoint (int)
|
||||
TARGET_DEFAULT_RETURN (1);
|
||||
virtual int remove_exec_catchpoint (int)
|
||||
@@ -729,6 +731,10 @@ struct target_ops
|
||||
TARGET_DEFAULT_RETURN (false);
|
||||
virtual void thread_events (int)
|
||||
TARGET_DEFAULT_IGNORE ();
|
||||
/* Returns true if the target supports setting thread options
|
||||
OPTIONS, false otherwise. */
|
||||
virtual bool supports_set_thread_options (gdb_thread_options options)
|
||||
TARGET_DEFAULT_RETURN (false);
|
||||
/* This method must be implemented in some situations. See the
|
||||
comment on 'can_run'. */
|
||||
virtual bool supports_non_stop ()
|
||||
@@ -1892,6 +1898,10 @@ extern void target_async (bool enable);
|
||||
/* Enables/disables thread create and exit events. */
|
||||
extern void target_thread_events (int enable);
|
||||
|
||||
/* Returns true if the target supports setting thread options
|
||||
OPTIONS. */
|
||||
extern bool target_supports_set_thread_options (gdb_thread_options options);
|
||||
|
||||
/* Whether support for controlling the target backends always in
|
||||
non-stop mode is enabled. */
|
||||
extern enum auto_boolean target_non_stop_enabled;
|
||||
|
||||
@@ -188,3 +188,15 @@ target_read_string (CORE_ADDR memaddr, int len, int *bytes_read)
|
||||
|
||||
return gdb::unique_xmalloc_ptr<char> ((char *) buffer.release ());
|
||||
}
|
||||
|
||||
/* See target/target.h. */
|
||||
|
||||
std::string
|
||||
to_string (gdb_thread_options options)
|
||||
{
|
||||
static constexpr gdb_thread_options::string_mapping mapping[] = {
|
||||
MAP_ENUM_FLAG (GDB_THREAD_OPTION_CLONE),
|
||||
MAP_ENUM_FLAG (GDB_THREAD_OPTION_EXIT),
|
||||
};
|
||||
return options.to_string (mapping);
|
||||
}
|
||||
|
||||
@@ -22,9 +22,29 @@
|
||||
|
||||
#include "target/waitstatus.h"
|
||||
#include "target/wait.h"
|
||||
#include "gdbsupport/enum-flags.h"
|
||||
|
||||
/* This header is a stopgap until more code is shared. */
|
||||
|
||||
/* Available thread options. Keep this in sync with to_string, in
|
||||
target.c. */
|
||||
|
||||
enum gdb_thread_option : unsigned
|
||||
{
|
||||
/* Tell the target to report TARGET_WAITKIND_THREAD_CLONED events
|
||||
for the thread. */
|
||||
GDB_THREAD_OPTION_CLONE = 1 << 0,
|
||||
|
||||
/* Tell the target to report TARGET_WAITKIND_THREAD_EXIT events for
|
||||
the thread. */
|
||||
GDB_THREAD_OPTION_EXIT = 1 << 1,
|
||||
};
|
||||
|
||||
DEF_ENUM_FLAGS_TYPE (enum gdb_thread_option, gdb_thread_options);
|
||||
|
||||
/* Convert gdb_thread_option to a string. */
|
||||
extern std::string to_string (gdb_thread_options options);
|
||||
|
||||
/* Read LEN bytes of target memory at address MEMADDR, placing the
|
||||
results in GDB's memory at MYADDR. Return zero for success,
|
||||
nonzero if any error occurs. This function must be provided by
|
||||
|
||||
@@ -45,6 +45,7 @@ DIAGNOSTIC_ERROR_SWITCH
|
||||
|
||||
case TARGET_WAITKIND_FORKED:
|
||||
case TARGET_WAITKIND_VFORKED:
|
||||
case TARGET_WAITKIND_THREAD_CLONED:
|
||||
return string_appendf (str, ", child_ptid = %s",
|
||||
this->child_ptid ().to_string ().c_str ());
|
||||
|
||||
|
||||
@@ -95,6 +95,13 @@ enum target_waitkind
|
||||
/* There are no resumed children left in the program. */
|
||||
TARGET_WAITKIND_NO_RESUMED,
|
||||
|
||||
/* The thread was cloned. The event's ptid corresponds to the
|
||||
cloned parent. The cloned child is held stopped at its entry
|
||||
point, and its ptid is in the event's m_child_ptid. The target
|
||||
must not add the cloned child to GDB's thread list until
|
||||
target_ops::follow_clone() is called. */
|
||||
TARGET_WAITKIND_THREAD_CLONED,
|
||||
|
||||
/* The thread was created. */
|
||||
TARGET_WAITKIND_THREAD_CREATED,
|
||||
|
||||
@@ -102,6 +109,17 @@ enum target_waitkind
|
||||
TARGET_WAITKIND_THREAD_EXITED,
|
||||
};
|
||||
|
||||
/* Determine if KIND represents an event with a new child - a fork,
|
||||
vfork, or clone. */
|
||||
|
||||
static inline bool
|
||||
is_new_child_status (target_waitkind kind)
|
||||
{
|
||||
return (kind == TARGET_WAITKIND_FORKED
|
||||
|| kind == TARGET_WAITKIND_VFORKED
|
||||
|| kind == TARGET_WAITKIND_THREAD_CLONED);
|
||||
}
|
||||
|
||||
/* Return KIND as a string. */
|
||||
|
||||
static inline const char *
|
||||
@@ -125,6 +143,8 @@ DIAGNOSTIC_ERROR_SWITCH
|
||||
return "FORKED";
|
||||
case TARGET_WAITKIND_VFORKED:
|
||||
return "VFORKED";
|
||||
case TARGET_WAITKIND_THREAD_CLONED:
|
||||
return "THREAD_CLONED";
|
||||
case TARGET_WAITKIND_EXECD:
|
||||
return "EXECD";
|
||||
case TARGET_WAITKIND_VFORK_DONE:
|
||||
@@ -325,6 +345,14 @@ struct target_waitstatus
|
||||
return *this;
|
||||
}
|
||||
|
||||
target_waitstatus &set_thread_cloned (ptid_t child_ptid)
|
||||
{
|
||||
this->reset ();
|
||||
m_kind = TARGET_WAITKIND_THREAD_CLONED;
|
||||
m_value.child_ptid = child_ptid;
|
||||
return *this;
|
||||
}
|
||||
|
||||
target_waitstatus &set_thread_created ()
|
||||
{
|
||||
this->reset ();
|
||||
@@ -369,8 +397,7 @@ struct target_waitstatus
|
||||
|
||||
ptid_t child_ptid () const
|
||||
{
|
||||
gdb_assert (m_kind == TARGET_WAITKIND_FORKED
|
||||
|| m_kind == TARGET_WAITKIND_VFORKED);
|
||||
gdb_assert (is_new_child_status (m_kind));
|
||||
return m_value.child_ptid;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,16 +31,25 @@ proc catch_follow_exec { } {
|
||||
return -1
|
||||
}
|
||||
|
||||
gdb_test "catch exec" \
|
||||
{Catchpoint [0-9]+ \(exec\)}
|
||||
set bpnum ""
|
||||
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 "continue" \
|
||||
".*hit Catchpoint.*"
|
||||
"Thread 2.1 .*hit Catchpoint $bpnum.*"
|
||||
|
||||
set any "\[^\r\n\]*"
|
||||
|
||||
gdb_test "info prog" \
|
||||
"No selected thread."
|
||||
"Last stopped for thread 2.1 \\\($any\\\)\\..*It stopped at breakpoint $bpnum\\..*"
|
||||
}
|
||||
|
||||
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
|
||||
# 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 srcfile
|
||||
global reading_in_symbols_re
|
||||
|
||||
if { $second_inferior && [use_gdb_stub] } {
|
||||
return
|
||||
}
|
||||
|
||||
if { ![setup] } {
|
||||
return
|
||||
}
|
||||
|
||||
# Get rid of the breakpoint at "main".
|
||||
delete_breakpoints
|
||||
|
||||
set bp_after_fork [gdb_get_line_number "set breakpoint here"]
|
||||
|
||||
gdb_test "catch fork" \
|
||||
@@ -249,6 +256,29 @@ proc_with_prefix catch_fork_child_follow {} {
|
||||
"Temporary breakpoint.*, line $bp_after_fork.*" \
|
||||
"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"
|
||||
append expected_re ".* at .*$bp_after_fork.*"
|
||||
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.
|
||||
|
||||
catch_fork_child_follow
|
||||
foreach_with_prefix second_inferior {false true} {
|
||||
catch_fork_child_follow $second_inferior
|
||||
}
|
||||
catch_fork_unpatch_child
|
||||
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
|
||||
# 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] } {
|
||||
return -1
|
||||
standard_testfile
|
||||
|
||||
# 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] } {
|
||||
return -1
|
||||
# Build executables and test them, one for each
|
||||
# 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 } {
|
||||
global gdb_prompt
|
||||
|
||||
set syscall_insn_next_addr_found [get_hexadecimal_valueof "\$pc" "0"]
|
||||
|
||||
# After the 'stepi' we expect thread 1 to still be selected.
|
||||
# However, when displaced stepping over a clone bug gdb/19675
|
||||
# means this might not be the case.
|
||||
#
|
||||
# Which thread we end up in depends on a race between the original
|
||||
# thread-1, and the new thread (created by the clone), so we can't
|
||||
# guarantee which thread we will be in at this point.
|
||||
#
|
||||
# For the fork/vfork syscalls, which are correctly handled by
|
||||
# displaced stepping we will always be in thread-1 or the original
|
||||
# process at this point.
|
||||
set curr_thread "unknown"
|
||||
gdb_test_multiple "info threads" "" {
|
||||
-re "Id\\s+Target Id\\s+Frame\\s*\r\n" {
|
||||
exp_continue
|
||||
}
|
||||
-re "^\\* (\\d+)\\s+\[^\r\n\]+\r\n" {
|
||||
gdb_test_multiple "thread" "" {
|
||||
-re -wrap "Current thread is (\\d+) .*" {
|
||||
set curr_thread $expect_out(1,string)
|
||||
exp_continue
|
||||
pass $gdb_test_name
|
||||
}
|
||||
-re "^\\s+\\d+\\s+\[^\r\n\]+\r\n" {
|
||||
exp_continue
|
||||
}
|
||||
-re "$gdb_prompt " {
|
||||
}
|
||||
}
|
||||
|
||||
# If we are displaced stepping over a clone, and we ended up in
|
||||
# the wrong thread then the following check of the $pc value will
|
||||
# fail.
|
||||
if { $displaced == "on" && $syscall == "clone" && $curr_thread != 1 } {
|
||||
# GDB doesn't support stepping over clone syscall with
|
||||
# displaced stepping.
|
||||
setup_kfail "*-*-*" "gdb/19675"
|
||||
}
|
||||
|
||||
gdb_assert {$syscall_insn_next_addr != 0 \
|
||||
@@ -299,15 +270,6 @@ proc step_over_syscall { syscall } {
|
||||
|
||||
gdb_test "break marker" "Breakpoint.*at.* file .*${testfile}.c, line.*"
|
||||
|
||||
# If we are displaced stepping over a clone syscall then
|
||||
# we expect the following check to fail. See also the
|
||||
# code in check_pc_after_cross_syscall.
|
||||
if { $displaced == "on" && $syscall == "clone" } {
|
||||
# GDB doesn't support stepping over clone syscall with
|
||||
# displaced stepping.
|
||||
setup_kfail "*-*-*" "gdb/19675"
|
||||
}
|
||||
|
||||
gdb_test "continue" "Continuing\\..*Breakpoint \[0-9\]+, marker \\(\\) at.*" \
|
||||
"continue to marker ($syscall)"
|
||||
}
|
||||
|
||||
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_test "continue" "Breakpoint $decimal, foo .*" \
|
||||
"continue to foo"
|
||||
|
||||
# Test that GDB is able to kill the inferior. This may fail if
|
||||
# e.g., GDB does not dispose of the pre-exec threads properly.
|
||||
gdb_test "with confirm off -- kill" \
|
||||
"\\\[Inferior 1 (.*) killed\\\]" \
|
||||
"kill inferior"
|
||||
}
|
||||
|
||||
foreach_with_prefix displaced_stepping {auto off} {
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/* This testcase is part of GDB, the GNU debugger.
|
||||
|
||||
Copyright 2021-2022 Free Software Foundation, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include "../lib/my-syscalls.h"
|
||||
|
||||
#define NUM_THREADS 32
|
||||
|
||||
static void *
|
||||
stepper_over_exit_thread (void *v)
|
||||
{
|
||||
my_exit (0);
|
||||
|
||||
/* my_exit above should exit the thread, we don't expect to reach
|
||||
here. */
|
||||
abort ();
|
||||
}
|
||||
|
||||
static void *
|
||||
spawner_thread (void *v)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
pthread_t threads[NUM_THREADS];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NUM_THREADS; i++)
|
||||
pthread_create (&threads[i], NULL, stepper_over_exit_thread, NULL);
|
||||
|
||||
for (i = 0; i < NUM_THREADS; i++)
|
||||
pthread_join (threads[i], NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
break_here (void)
|
||||
{
|
||||
}
|
||||
|
||||
static void *
|
||||
breakpoint_hitter_thread (void *v)
|
||||
{
|
||||
for (;;)
|
||||
break_here ();
|
||||
}
|
||||
|
||||
int
|
||||
main ()
|
||||
{
|
||||
pthread_t breakpoint_hitter;
|
||||
pthread_t spawner;
|
||||
|
||||
alarm (60);
|
||||
|
||||
pthread_create (&spawner, NULL, spawner_thread, NULL);
|
||||
pthread_create (&breakpoint_hitter, NULL, breakpoint_hitter_thread, NULL);
|
||||
|
||||
pthread_join (spawner, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
# Copyright 2021-2022 Free Software Foundation, Inc.
|
||||
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Test stepping over a breakpoint installed on an instruction that
|
||||
# exits the thread, while another thread is repeatedly hitting a
|
||||
# breakpoint, causing GDB to stop all threads.
|
||||
|
||||
standard_testfile .c
|
||||
|
||||
set syscalls_src $srcdir/lib/my-syscalls.S
|
||||
|
||||
if { [build_executable "failed to prepare" $testfile \
|
||||
[list $srcfile $syscalls_src] {debug pthreads}] == -1 } {
|
||||
return
|
||||
}
|
||||
|
||||
proc test {displaced-stepping target-non-stop} {
|
||||
save_vars ::GDBFLAGS {
|
||||
append ::GDBFLAGS " -ex \"maintenance set target-non-stop ${target-non-stop}\""
|
||||
clean_restart $::binfile
|
||||
}
|
||||
|
||||
gdb_test_no_output "set displaced-stepping ${displaced-stepping}"
|
||||
|
||||
if { ![runto_main] } {
|
||||
return
|
||||
}
|
||||
|
||||
# The "stepper over exit" threads will step over an instruction
|
||||
# that causes them to exit.
|
||||
gdb_test "break my_exit_syscall if 0"
|
||||
|
||||
# The "breakpoint hitter" thread will repeatedly hit this
|
||||
# breakpoint, causing GDB to stop all threads.
|
||||
gdb_test "break break_here"
|
||||
|
||||
# To avoid flooding the log with thread created/exited messages.
|
||||
gdb_test_no_output "set print thread-events off"
|
||||
|
||||
# Make sure the target reports the breakpoint stops.
|
||||
gdb_test_no_output "set breakpoint condition-evaluation host"
|
||||
|
||||
for { set i 0 } { $i < 30 } { incr i } {
|
||||
with_test_prefix "iter $i" {
|
||||
if { [gdb_test "continue" "hit Breakpoint $::decimal, break_here .*"] != 0 } {
|
||||
# Exit if there's a failure to avoid lengthy timeouts.
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach_with_prefix displaced-stepping {off auto} {
|
||||
foreach_with_prefix target-non-stop {off on} {
|
||||
test ${displaced-stepping} ${target-non-stop}
|
||||
}
|
||||
}
|
||||
52
gdb/testsuite/gdb.threads/step-over-thread-exit.c
Normal file
52
gdb/testsuite/gdb.threads/step-over-thread-exit.c
Normal file
@@ -0,0 +1,52 @@
|
||||
/* This testcase is part of GDB, the GNU debugger.
|
||||
|
||||
Copyright 2021-2022 Free Software Foundation, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include <pthread.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include "../lib/my-syscalls.h"
|
||||
|
||||
static void *
|
||||
thread_func (void *arg)
|
||||
{
|
||||
my_exit (0);
|
||||
|
||||
/* my_exit above should exit the thread, we don't expect to reach
|
||||
here. */
|
||||
abort ();
|
||||
}
|
||||
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Spawn and join a thread, 100 times. */
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
pthread_t thread;
|
||||
int ret;
|
||||
|
||||
ret = pthread_create (&thread, NULL, thread_func, NULL);
|
||||
assert (ret == 0);
|
||||
|
||||
ret = pthread_join (thread, NULL);
|
||||
assert (ret == 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
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>
|
||||
|
||||
/* int my_execve (const char *file, char *argv[], char *envp[]); */
|
||||
|
||||
.global my_execve
|
||||
my_execve:
|
||||
/* The SYSCALL macro below current supports calling syscalls with up
|
||||
to 3 arguments, and, assumes the syscall never returns, like exec
|
||||
and exit. If you need to call syscalls with more arguments or you
|
||||
need to call syscalls that actually return, you'll need to update
|
||||
the macros. We don't bother with optimizing setting up fewer
|
||||
arguments for syscalls that take fewer arguments, as we're not
|
||||
optimizating for speed or space, but for maintainability. */
|
||||
|
||||
#if defined(__x86_64__)
|
||||
|
||||
mov $__NR_execve, %rax
|
||||
/* rdi, rsi and rdx already contain the right arguments. */
|
||||
my_execve_syscall:
|
||||
syscall
|
||||
ret
|
||||
#define SYSCALL(NAME, NR) \
|
||||
.global NAME ;\
|
||||
NAME: ;\
|
||||
mov $NR, %rax ;\
|
||||
/* rdi, rsi and rdx already contain the right arguments. */ \
|
||||
NAME ## _syscall: ;\
|
||||
syscall ;\
|
||||
ret ;
|
||||
|
||||
#elif defined(__i386__)
|
||||
|
||||
mov $__NR_execve, %eax
|
||||
mov 4(%esp), %ebx
|
||||
mov 8(%esp), %ecx
|
||||
mov 12(%esp), %edx
|
||||
my_execve_syscall:
|
||||
int $0x80
|
||||
#define SYSCALL(NAME, NR) \
|
||||
.global NAME ;\
|
||||
NAME: ;\
|
||||
mov $NR, %eax ;\
|
||||
mov 4(%esp), %ebx ;\
|
||||
mov 8(%esp), %ecx ;\
|
||||
mov 12(%esp), %edx ;\
|
||||
NAME ## _syscall: ;\
|
||||
int $0x80 ;\
|
||||
ret
|
||||
|
||||
#elif defined(__aarch64__)
|
||||
|
||||
mov x8, #__NR_execve
|
||||
/* x0, x1 and x2 already contain the right arguments. */
|
||||
my_execve_syscall:
|
||||
#define SYSCALL(NAME, NR) \
|
||||
.global NAME ;\
|
||||
NAME: ;\
|
||||
mov x8, NR ;\
|
||||
/* x0, x1 and x2 already contain the right arguments. */ \
|
||||
NAME ## _syscall: ;\
|
||||
svc #0
|
||||
|
||||
#else
|
||||
# error "Unsupported architecture"
|
||||
#endif
|
||||
|
||||
SYSCALL (my_execve, __NR_execve)
|
||||
|
||||
/* void my_exit (int code); */
|
||||
|
||||
SYSCALL (my_exit, __NR_exit)
|
||||
|
||||
.section .note.GNU-stack,"",@progbits
|
||||
|
||||
@@ -22,4 +22,9 @@
|
||||
|
||||
int my_execve (const char *file, char *argv[], char *envp[]);
|
||||
|
||||
/* `exit` syscall, which makes the thread exit (as opposed to
|
||||
`exit_group`, which makes the process exit). */
|
||||
|
||||
void my_exit (int code);
|
||||
|
||||
#endif /* MY_SYSCALLS_H */
|
||||
|
||||
71
gdb/thread.c
71
gdb/thread.c
@@ -192,7 +192,8 @@ clear_thread_inferior_resources (struct thread_info *tp)
|
||||
/* See gdbthread.h. */
|
||||
|
||||
void
|
||||
set_thread_exited (thread_info *tp, bool silent)
|
||||
set_thread_exited (thread_info *tp, gdb::optional<ULONGEST> exit_code,
|
||||
bool silent)
|
||||
{
|
||||
/* Dead threads don't need to step-over. Remove from chain. */
|
||||
if (thread_is_in_step_over_chain (tp))
|
||||
@@ -211,7 +212,22 @@ set_thread_exited (thread_info *tp, bool silent)
|
||||
if (proc_target != nullptr)
|
||||
proc_target->maybe_remove_resumed_with_pending_wait_status (tp);
|
||||
|
||||
gdb::observers::thread_exit.notify (tp, silent);
|
||||
if (!silent && print_thread_events)
|
||||
{
|
||||
if (exit_code.has_value ())
|
||||
{
|
||||
gdb_printf (_("[%s exited with code %s]\n"),
|
||||
target_pid_to_str (tp->ptid).c_str (),
|
||||
pulongest (*exit_code));
|
||||
}
|
||||
else
|
||||
{
|
||||
gdb_printf (_("[%s exited]\n"),
|
||||
target_pid_to_str (tp->ptid).c_str ());
|
||||
}
|
||||
}
|
||||
|
||||
gdb::observers::thread_exit.notify (tp, exit_code, silent);
|
||||
|
||||
/* Tag it as exited. */
|
||||
tp->state = THREAD_EXITED;
|
||||
@@ -235,7 +251,7 @@ init_thread_list (void)
|
||||
highest_thread_num = 0;
|
||||
|
||||
for (inferior *inf : all_inferiors ())
|
||||
inf->clear_thread_list (true);
|
||||
inf->clear_thread_list ();
|
||||
}
|
||||
|
||||
/* Allocate a new thread of inferior INF with target id PTID and add
|
||||
@@ -398,6 +414,24 @@ thread_info::clear_pending_waitstatus ()
|
||||
|
||||
/* See gdbthread.h. */
|
||||
|
||||
void
|
||||
thread_info::set_thread_options (gdb_thread_options thread_options)
|
||||
{
|
||||
gdb_assert (this->state != THREAD_EXITED);
|
||||
gdb_assert (!this->executing ());
|
||||
|
||||
if (m_thread_options == thread_options)
|
||||
return;
|
||||
|
||||
m_thread_options = thread_options;
|
||||
|
||||
infrun_debug_printf ("[options for %s are now %s]",
|
||||
this->ptid.to_string ().c_str (),
|
||||
to_string (thread_options).c_str ());
|
||||
}
|
||||
|
||||
/* See gdbthread.h. */
|
||||
|
||||
int
|
||||
thread_is_in_step_over_chain (struct thread_info *tp)
|
||||
{
|
||||
@@ -450,20 +484,22 @@ global_thread_step_over_chain_remove (struct thread_info *tp)
|
||||
global_thread_step_over_list.erase (it);
|
||||
}
|
||||
|
||||
/* Delete the thread referenced by THR. If SILENT, don't notify
|
||||
the observer of this exit.
|
||||
|
||||
THR must not be NULL or a failed assertion will be raised. */
|
||||
/* Helper for the different delete_thread variants. */
|
||||
|
||||
static void
|
||||
delete_thread_1 (thread_info *thr, bool silent)
|
||||
delete_thread_1 (thread_info *thr, gdb::optional<ULONGEST> exit_code,
|
||||
bool silent)
|
||||
{
|
||||
gdb_assert (thr != nullptr);
|
||||
|
||||
threads_debug_printf ("deleting thread %s, silent = %d",
|
||||
thr->ptid.to_string ().c_str (), silent);
|
||||
threads_debug_printf ("deleting thread %s, exit_code = %s, silent = %d",
|
||||
thr->ptid.to_string ().c_str (),
|
||||
(exit_code.has_value ()
|
||||
? pulongest (*exit_code)
|
||||
: "<none>"),
|
||||
silent);
|
||||
|
||||
set_thread_exited (thr, silent);
|
||||
set_thread_exited (thr, exit_code, silent);
|
||||
|
||||
if (!thr->deletable ())
|
||||
{
|
||||
@@ -479,16 +515,25 @@ delete_thread_1 (thread_info *thr, bool silent)
|
||||
|
||||
/* See gdbthread.h. */
|
||||
|
||||
void
|
||||
delete_thread_with_exit_code (thread_info *thread, ULONGEST exit_code,
|
||||
bool silent)
|
||||
{
|
||||
delete_thread_1 (thread, exit_code, false /* not silent */);
|
||||
}
|
||||
|
||||
/* See gdbthread.h. */
|
||||
|
||||
void
|
||||
delete_thread (thread_info *thread)
|
||||
{
|
||||
delete_thread_1 (thread, false /* not silent */);
|
||||
delete_thread_1 (thread, {}, false /* not silent */);
|
||||
}
|
||||
|
||||
void
|
||||
delete_thread_silent (thread_info *thread)
|
||||
{
|
||||
delete_thread_1 (thread, true /* silent */);
|
||||
delete_thread_1 (thread, {}, true /* not silent */);
|
||||
}
|
||||
|
||||
struct thread_info *
|
||||
|
||||
@@ -359,21 +359,47 @@ CHECK_VALID (true, bool, NF (1) == char (1))
|
||||
|
||||
enum test_flag
|
||||
{
|
||||
FLAG1 = 1 << 1,
|
||||
FLAG2 = 1 << 2,
|
||||
FLAG3 = 1 << 3,
|
||||
FLAG1 = 1 << 0,
|
||||
FLAG2 = 1 << 1,
|
||||
FLAG3 = 1 << 2,
|
||||
FLAG4 = 1 << 3,
|
||||
};
|
||||
|
||||
enum test_uflag : unsigned
|
||||
{
|
||||
UFLAG1 = 1 << 1,
|
||||
UFLAG2 = 1 << 2,
|
||||
UFLAG3 = 1 << 3,
|
||||
UFLAG1 = 1 << 0,
|
||||
UFLAG2 = 1 << 1,
|
||||
UFLAG3 = 1 << 2,
|
||||
UFLAG4 = 1 << 3,
|
||||
};
|
||||
|
||||
DEF_ENUM_FLAGS_TYPE (test_flag, test_flags);
|
||||
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
|
||||
self_test ()
|
||||
{
|
||||
@@ -581,6 +607,37 @@ self_test ()
|
||||
|
||||
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 */
|
||||
|
||||
@@ -611,21 +611,13 @@ windows_nat_target::delete_thread (ptid_t ptid, DWORD exit_code,
|
||||
|
||||
id = ptid.lwp ();
|
||||
|
||||
/* Emit a notification about the thread being deleted.
|
||||
|
||||
Note that no notification was printed when the main thread
|
||||
/* Note that no notification was printed when the main thread
|
||||
was created, and thus, unless in verbose mode, we should be
|
||||
symmetrical, and avoid that notification for the main thread
|
||||
here as well. */
|
||||
|
||||
if (info_verbose)
|
||||
gdb_printf ("[Deleting %s]\n", target_pid_to_str (ptid).c_str ());
|
||||
else if (print_thread_events && !main_thread_p)
|
||||
gdb_printf (_("[%s exited with code %u]\n"),
|
||||
target_pid_to_str (ptid).c_str (),
|
||||
(unsigned) exit_code);
|
||||
|
||||
::delete_thread (find_thread_ptid (this, ptid));
|
||||
bool silent = (main_thread_p && !info_verbose);
|
||||
thread_info *todel = find_thread_ptid (this, ptid);
|
||||
delete_thread_with_exit_code (todel, exit_code, silent);
|
||||
|
||||
auto iter = std::find_if (windows_process.thread_list.begin (),
|
||||
windows_process.thread_list.end (),
|
||||
@@ -2157,7 +2149,7 @@ windows_nat_target::files_info ()
|
||||
|
||||
gdb_printf ("\tUsing the running image of %s %s.\n",
|
||||
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.
|
||||
|
||||
@@ -80,6 +80,9 @@ struct thread_info
|
||||
|
||||
/* Branch trace target information for this thread. */
|
||||
struct btrace_target_info *btrace = nullptr;
|
||||
|
||||
/* Thread options GDB requested with QThreadOptions. */
|
||||
gdb_thread_options thread_options = 0;
|
||||
};
|
||||
|
||||
extern std::list<thread_info *> all_threads;
|
||||
|
||||
@@ -144,6 +144,18 @@ is_leader (thread_info *thread)
|
||||
return ptid.pid () == ptid.lwp ();
|
||||
}
|
||||
|
||||
/* Return true if we should report thread exit events to GDB, for
|
||||
THR. */
|
||||
|
||||
static bool
|
||||
report_exit_events_for (thread_info *thr)
|
||||
{
|
||||
client_state &cs = get_client_state ();
|
||||
|
||||
return (cs.report_thread_events
|
||||
|| (thr->thread_options & GDB_THREAD_OPTION_EXIT) != 0);
|
||||
}
|
||||
|
||||
/* LWP accessors. */
|
||||
|
||||
/* See nat/linux-nat.h. */
|
||||
@@ -267,7 +279,8 @@ int using_threads = 1;
|
||||
static int stabilizing_threads;
|
||||
|
||||
static void unsuspend_all_lwps (struct lwp_info *except);
|
||||
static void mark_lwp_dead (struct lwp_info *lwp, int wstat);
|
||||
static void mark_lwp_dead (struct lwp_info *lwp, int wstat,
|
||||
bool thread_event);
|
||||
static int lwp_is_marked_dead (struct lwp_info *lwp);
|
||||
static int kill_lwp (unsigned long lwpid, int signo);
|
||||
static void enqueue_pending_signal (struct lwp_info *lwp, int signal, siginfo_t *info);
|
||||
@@ -491,7 +504,6 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
|
||||
struct lwp_info *event_lwp = *orig_event_lwp;
|
||||
int event = linux_ptrace_get_extended_event (wstat);
|
||||
struct thread_info *event_thr = get_lwp_thread (event_lwp);
|
||||
struct lwp_info *new_lwp;
|
||||
|
||||
gdb_assert (event_lwp->waitstatus.kind () == TARGET_WAITKIND_IGNORE);
|
||||
|
||||
@@ -503,7 +515,6 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
|
||||
if ((event == PTRACE_EVENT_FORK) || (event == PTRACE_EVENT_VFORK)
|
||||
|| (event == PTRACE_EVENT_CLONE))
|
||||
{
|
||||
ptid_t ptid;
|
||||
unsigned long new_pid;
|
||||
int ret, status;
|
||||
|
||||
@@ -527,61 +538,65 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
|
||||
warning ("wait returned unexpected status 0x%x", status);
|
||||
}
|
||||
|
||||
if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
|
||||
if (debug_threads)
|
||||
{
|
||||
struct process_info *parent_proc;
|
||||
struct process_info *child_proc;
|
||||
struct lwp_info *child_lwp;
|
||||
struct thread_info *child_thr;
|
||||
debug_printf ("HEW: Got %s event from LWP %ld, new child is %ld\n",
|
||||
(event == PTRACE_EVENT_FORK ? "fork"
|
||||
: event == PTRACE_EVENT_VFORK ? "vfork"
|
||||
: event == PTRACE_EVENT_CLONE ? "clone"
|
||||
: "???"),
|
||||
ptid_of (event_thr).lwp (),
|
||||
new_pid);
|
||||
}
|
||||
|
||||
ptid = ptid_t (new_pid, new_pid);
|
||||
ptid_t child_ptid = (event != PTRACE_EVENT_CLONE
|
||||
? ptid_t (new_pid, new_pid)
|
||||
: ptid_t (ptid_of (event_thr).pid (), new_pid));
|
||||
|
||||
threads_debug_printf ("Got fork event from LWP %ld, "
|
||||
"new child is %d",
|
||||
ptid_of (event_thr).lwp (),
|
||||
ptid.pid ());
|
||||
lwp_info *child_lwp = add_lwp (child_ptid);
|
||||
gdb_assert (child_lwp != NULL);
|
||||
child_lwp->stopped = 1;
|
||||
if (event != PTRACE_EVENT_CLONE)
|
||||
child_lwp->must_set_ptrace_flags = 1;
|
||||
child_lwp->status_pending_p = 0;
|
||||
|
||||
thread_info *child_thr = get_lwp_thread (child_lwp);
|
||||
|
||||
/* If we're suspending all threads, leave this one suspended
|
||||
too. If the fork/clone parent is stepping over a breakpoint,
|
||||
all other threads have been suspended already. Leave the
|
||||
child suspended too. */
|
||||
if (stopping_threads == STOPPING_AND_SUSPENDING_THREADS
|
||||
|| event_lwp->bp_reinsert != 0)
|
||||
{
|
||||
threads_debug_printf ("leaving child suspended");
|
||||
child_lwp->suspended = 1;
|
||||
}
|
||||
|
||||
if (event_lwp->bp_reinsert != 0
|
||||
&& supports_software_single_step ()
|
||||
&& event == PTRACE_EVENT_VFORK)
|
||||
{
|
||||
/* If we leave single-step breakpoints there, child will
|
||||
hit it, so uninsert single-step breakpoints from parent
|
||||
(and child). Once vfork child is done, reinsert
|
||||
them back to parent. */
|
||||
uninsert_single_step_breakpoints (event_thr);
|
||||
}
|
||||
|
||||
if (event != PTRACE_EVENT_CLONE)
|
||||
{
|
||||
/* Add the new process to the tables and clone the breakpoint
|
||||
lists of the parent. We need to do this even if the new process
|
||||
will be detached, since we will need the process object and the
|
||||
breakpoints to remove any breakpoints from memory when we
|
||||
detach, and the client side will access registers. */
|
||||
child_proc = add_linux_process (new_pid, 0);
|
||||
process_info *child_proc = add_linux_process (new_pid, 0);
|
||||
gdb_assert (child_proc != NULL);
|
||||
child_lwp = add_lwp (ptid);
|
||||
gdb_assert (child_lwp != NULL);
|
||||
child_lwp->stopped = 1;
|
||||
child_lwp->must_set_ptrace_flags = 1;
|
||||
child_lwp->status_pending_p = 0;
|
||||
child_thr = get_lwp_thread (child_lwp);
|
||||
child_thr->last_resume_kind = resume_stop;
|
||||
child_thr->last_status.set_stopped (GDB_SIGNAL_0);
|
||||
|
||||
/* If we're suspending all threads, leave this one suspended
|
||||
too. If the fork/clone parent is stepping over a breakpoint,
|
||||
all other threads have been suspended already. Leave the
|
||||
child suspended too. */
|
||||
if (stopping_threads == STOPPING_AND_SUSPENDING_THREADS
|
||||
|| event_lwp->bp_reinsert != 0)
|
||||
{
|
||||
threads_debug_printf ("leaving child suspended");
|
||||
child_lwp->suspended = 1;
|
||||
}
|
||||
|
||||
parent_proc = get_thread_process (event_thr);
|
||||
process_info *parent_proc = get_thread_process (event_thr);
|
||||
child_proc->attached = parent_proc->attached;
|
||||
|
||||
if (event_lwp->bp_reinsert != 0
|
||||
&& supports_software_single_step ()
|
||||
&& event == PTRACE_EVENT_VFORK)
|
||||
{
|
||||
/* If we leave single-step breakpoints there, child will
|
||||
hit it, so uninsert single-step breakpoints from parent
|
||||
(and child). Once vfork child is done, reinsert
|
||||
them back to parent. */
|
||||
uninsert_single_step_breakpoints (event_thr);
|
||||
}
|
||||
|
||||
clone_all_breakpoints (child_thr, event_thr);
|
||||
|
||||
target_desc_up tdesc = allocate_target_description ();
|
||||
@@ -590,88 +605,97 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
|
||||
|
||||
/* Clone arch-specific process data. */
|
||||
low_new_fork (parent_proc, child_proc);
|
||||
}
|
||||
|
||||
/* Save fork info in the parent thread. */
|
||||
if (event == PTRACE_EVENT_FORK)
|
||||
event_lwp->waitstatus.set_forked (ptid);
|
||||
else if (event == PTRACE_EVENT_VFORK)
|
||||
event_lwp->waitstatus.set_vforked (ptid);
|
||||
/* Save fork/clone info in the parent thread. */
|
||||
if (event == PTRACE_EVENT_FORK)
|
||||
event_lwp->waitstatus.set_forked (child_ptid);
|
||||
else if (event == PTRACE_EVENT_VFORK)
|
||||
event_lwp->waitstatus.set_vforked (child_ptid);
|
||||
else if (event == PTRACE_EVENT_CLONE
|
||||
&& (event_thr->thread_options & GDB_THREAD_OPTION_CLONE) != 0)
|
||||
event_lwp->waitstatus.set_thread_cloned (child_ptid);
|
||||
|
||||
if (event != PTRACE_EVENT_CLONE
|
||||
|| (event_thr->thread_options & GDB_THREAD_OPTION_CLONE) != 0)
|
||||
{
|
||||
/* The status_pending field contains bits denoting the
|
||||
extended event, so when the pending event is handled,
|
||||
the handler will look at lwp->waitstatus. */
|
||||
extended event, so when the pending event is handled, the
|
||||
handler will look at lwp->waitstatus. */
|
||||
event_lwp->status_pending_p = 1;
|
||||
event_lwp->status_pending = wstat;
|
||||
|
||||
/* Link the threads until the parent event is passed on to
|
||||
higher layers. */
|
||||
/* Link the threads until the parent's event is passed on to
|
||||
GDB. */
|
||||
event_lwp->fork_relative = child_lwp;
|
||||
child_lwp->fork_relative = event_lwp;
|
||||
|
||||
/* If the parent thread is doing step-over with single-step
|
||||
breakpoints, the list of single-step breakpoints are cloned
|
||||
from the parent's. Remove them from the child process.
|
||||
In case of vfork, we'll reinsert them back once vforked
|
||||
child is done. */
|
||||
if (event_lwp->bp_reinsert != 0
|
||||
&& supports_software_single_step ())
|
||||
{
|
||||
/* The child process is forked and stopped, so it is safe
|
||||
to access its memory without stopping all other threads
|
||||
from other processes. */
|
||||
delete_single_step_breakpoints (child_thr);
|
||||
|
||||
gdb_assert (has_single_step_breakpoints (event_thr));
|
||||
gdb_assert (!has_single_step_breakpoints (child_thr));
|
||||
}
|
||||
|
||||
/* Report the event. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
threads_debug_printf
|
||||
("Got clone event from LWP %ld, new child is LWP %ld",
|
||||
lwpid_of (event_thr), new_pid);
|
||||
/* If the parent thread is doing step-over with single-step
|
||||
breakpoints, the list of single-step breakpoints are cloned
|
||||
from the parent's. Remove them from the child process.
|
||||
In case of vfork, we'll reinsert them back once vforked
|
||||
child is done. */
|
||||
if (event_lwp->bp_reinsert != 0
|
||||
&& supports_software_single_step ())
|
||||
{
|
||||
/* The child process is forked and stopped, so it is safe
|
||||
to access its memory without stopping all other threads
|
||||
from other processes. */
|
||||
delete_single_step_breakpoints (child_thr);
|
||||
|
||||
ptid = ptid_t (pid_of (event_thr), new_pid);
|
||||
new_lwp = add_lwp (ptid);
|
||||
|
||||
/* Either we're going to immediately resume the new thread
|
||||
or leave it stopped. resume_one_lwp is a nop if it
|
||||
thinks the thread is currently running, so set this first
|
||||
before calling resume_one_lwp. */
|
||||
new_lwp->stopped = 1;
|
||||
|
||||
/* If we're suspending all threads, leave this one suspended
|
||||
too. If the fork/clone parent is stepping over a breakpoint,
|
||||
all other threads have been suspended already. Leave the
|
||||
child suspended too. */
|
||||
if (stopping_threads == STOPPING_AND_SUSPENDING_THREADS
|
||||
|| event_lwp->bp_reinsert != 0)
|
||||
new_lwp->suspended = 1;
|
||||
gdb_assert (has_single_step_breakpoints (event_thr));
|
||||
gdb_assert (!has_single_step_breakpoints (child_thr));
|
||||
}
|
||||
|
||||
/* Normally we will get the pending SIGSTOP. But in some cases
|
||||
we might get another signal delivered to the group first.
|
||||
If we do get another signal, be sure not to lose it. */
|
||||
if (WSTOPSIG (status) != SIGSTOP)
|
||||
{
|
||||
new_lwp->stop_expected = 1;
|
||||
new_lwp->status_pending_p = 1;
|
||||
new_lwp->status_pending = status;
|
||||
child_lwp->stop_expected = 1;
|
||||
child_lwp->status_pending_p = 1;
|
||||
child_lwp->status_pending = status;
|
||||
}
|
||||
else if (cs.report_thread_events)
|
||||
else if (event == PTRACE_EVENT_CLONE && cs.report_thread_events)
|
||||
{
|
||||
new_lwp->waitstatus.set_thread_created ();
|
||||
new_lwp->status_pending_p = 1;
|
||||
new_lwp->status_pending = status;
|
||||
child_lwp->waitstatus.set_thread_created ();
|
||||
child_lwp->status_pending_p = 1;
|
||||
child_lwp->status_pending = status;
|
||||
}
|
||||
|
||||
if (event == PTRACE_EVENT_CLONE)
|
||||
{
|
||||
#ifdef USE_THREAD_DB
|
||||
thread_db_notice_clone (event_thr, ptid);
|
||||
thread_db_notice_clone (event_thr, child_ptid);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Don't report the event. */
|
||||
return 1;
|
||||
if (event == PTRACE_EVENT_CLONE
|
||||
&& (event_thr->thread_options & GDB_THREAD_OPTION_CLONE) == 0)
|
||||
{
|
||||
threads_debug_printf
|
||||
("not reporting clone event from LWP %ld, new child is %ld\n",
|
||||
ptid_of (event_thr).lwp (),
|
||||
new_pid);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Leave the child stopped until GDB processes the parent
|
||||
event. */
|
||||
child_thr->last_resume_kind = resume_stop;
|
||||
child_thr->last_status.set_stopped (GDB_SIGNAL_0);
|
||||
|
||||
/* Report the event. */
|
||||
threads_debug_printf
|
||||
("reporting %s event from LWP %ld, new child is %ld\n",
|
||||
(event == PTRACE_EVENT_FORK ? "fork"
|
||||
: event == PTRACE_EVENT_VFORK ? "vfork"
|
||||
: event == PTRACE_EVENT_CLONE ? "clone"
|
||||
: "???"),
|
||||
ptid_of (event_thr).lwp (),
|
||||
new_pid);
|
||||
return 0;
|
||||
}
|
||||
else if (event == PTRACE_EVENT_VFORK_DONE)
|
||||
{
|
||||
@@ -1777,10 +1801,12 @@ iterate_over_lwps (ptid_t filter,
|
||||
return get_thread_lwp (thread);
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
linux_process_target::check_zombie_leaders ()
|
||||
{
|
||||
for_each_process ([this] (process_info *proc)
|
||||
bool new_pending_event = false;
|
||||
|
||||
for_each_process ([&] (process_info *proc)
|
||||
{
|
||||
pid_t leader_pid = pid_of (proc);
|
||||
lwp_info *leader_lp = find_lwp_pid (ptid_t (leader_pid));
|
||||
@@ -1849,9 +1875,19 @@ linux_process_target::check_zombie_leaders ()
|
||||
"(it exited, or another thread execd), "
|
||||
"deleting it.",
|
||||
leader_pid);
|
||||
delete_lwp (leader_lp);
|
||||
|
||||
thread_info *leader_thread = get_lwp_thread (leader_lp);
|
||||
if (report_exit_events_for (leader_thread))
|
||||
{
|
||||
mark_lwp_dead (leader_lp, W_EXITCODE (0, 0), true);
|
||||
new_pending_event = true;
|
||||
}
|
||||
else
|
||||
delete_lwp (leader_lp);
|
||||
}
|
||||
});
|
||||
|
||||
return new_pending_event;
|
||||
}
|
||||
|
||||
/* Callback for `find_thread'. Returns the first LWP that is not
|
||||
@@ -2219,7 +2255,6 @@ linux_low_ptrace_options (int attached)
|
||||
void
|
||||
linux_process_target::filter_event (int lwpid, int wstat)
|
||||
{
|
||||
client_state &cs = get_client_state ();
|
||||
struct lwp_info *child;
|
||||
struct thread_info *thread;
|
||||
int have_stop_pc = 0;
|
||||
@@ -2306,12 +2341,12 @@ linux_process_target::filter_event (int lwpid, int wstat)
|
||||
/* If this is not the leader LWP, then the exit signal was not
|
||||
the end of the debugged application and should be ignored,
|
||||
unless GDB wants to hear about thread exits. */
|
||||
if (cs.report_thread_events || is_leader (thread))
|
||||
if (report_exit_events_for (thread) || is_leader (thread))
|
||||
{
|
||||
/* Since events are serialized to GDB core, and we can't
|
||||
report this one right now. Leave the status pending for
|
||||
the next time we're able to report it. */
|
||||
mark_lwp_dead (child, wstat);
|
||||
mark_lwp_dead (child, wstat, false);
|
||||
return;
|
||||
}
|
||||
else
|
||||
@@ -2624,7 +2659,8 @@ linux_process_target::wait_for_event_filtered (ptid_t wait_ptid,
|
||||
|
||||
/* Check for zombie thread group leaders. Those can't be reaped
|
||||
until all other threads in the thread group are. */
|
||||
check_zombie_leaders ();
|
||||
if (check_zombie_leaders ())
|
||||
goto retry;
|
||||
|
||||
auto not_stopped = [&] (thread_info *thread)
|
||||
{
|
||||
@@ -2868,13 +2904,31 @@ ptid_t
|
||||
linux_process_target::filter_exit_event (lwp_info *event_child,
|
||||
target_waitstatus *ourstatus)
|
||||
{
|
||||
client_state &cs = get_client_state ();
|
||||
struct thread_info *thread = get_lwp_thread (event_child);
|
||||
ptid_t ptid = ptid_of (thread);
|
||||
|
||||
if (ourstatus->kind () == TARGET_WAITKIND_THREAD_EXITED)
|
||||
{
|
||||
/* We're reporting a thread exit for the leader. The exit was
|
||||
detected by check_zombie_leaders. */
|
||||
gdb_assert (is_leader (thread));
|
||||
gdb_assert (report_exit_events_for (thread));
|
||||
|
||||
delete_lwp (event_child);
|
||||
return ptid;
|
||||
}
|
||||
|
||||
/* Note we must filter TARGET_WAITKIND_SIGNALLED as well, otherwise
|
||||
if a non-leader thread exits with a signal, we'd report it to the
|
||||
core which would interpret it as the whole-process exiting.
|
||||
There is no TARGET_WAITKIND_THREAD_SIGNALLED event kind. */
|
||||
if (ourstatus->kind () != TARGET_WAITKIND_EXITED
|
||||
&& ourstatus->kind () != TARGET_WAITKIND_SIGNALLED)
|
||||
return ptid;
|
||||
|
||||
if (!is_leader (thread))
|
||||
{
|
||||
if (cs.report_thread_events)
|
||||
if (report_exit_events_for (thread))
|
||||
ourstatus->set_thread_exited (0);
|
||||
else
|
||||
ourstatus->set_ignore ();
|
||||
@@ -2934,7 +2988,6 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
||||
int report_to_gdb;
|
||||
int trace_event;
|
||||
int in_step_range;
|
||||
int any_resumed;
|
||||
|
||||
threads_debug_printf ("[%s]", target_pid_to_str (ptid).c_str ());
|
||||
|
||||
@@ -2948,23 +3001,7 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
||||
in_step_range = 0;
|
||||
ourstatus->set_ignore ();
|
||||
|
||||
auto status_pending_p_any = [&] (thread_info *thread)
|
||||
{
|
||||
return status_pending_p_callback (thread, minus_one_ptid);
|
||||
};
|
||||
|
||||
auto not_stopped = [&] (thread_info *thread)
|
||||
{
|
||||
return not_stopped_callback (thread, minus_one_ptid);
|
||||
};
|
||||
|
||||
/* Find a resumed LWP, if any. */
|
||||
if (find_thread (status_pending_p_any) != NULL)
|
||||
any_resumed = 1;
|
||||
else if (find_thread (not_stopped) != NULL)
|
||||
any_resumed = 1;
|
||||
else
|
||||
any_resumed = 0;
|
||||
bool was_any_resumed = any_resumed ();
|
||||
|
||||
if (step_over_bkpt == null_ptid)
|
||||
pid = wait_for_event (ptid, &w, options);
|
||||
@@ -2975,7 +3012,7 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
||||
pid = wait_for_event (step_over_bkpt, &w, options & ~WNOHANG);
|
||||
}
|
||||
|
||||
if (pid == 0 || (pid == -1 && !any_resumed))
|
||||
if (pid == 0 || (pid == -1 && !was_any_resumed))
|
||||
{
|
||||
gdb_assert (target_options & TARGET_WNOHANG);
|
||||
|
||||
@@ -3000,7 +3037,20 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
||||
{
|
||||
if (WIFEXITED (w))
|
||||
{
|
||||
ourstatus->set_exited (WEXITSTATUS (w));
|
||||
/* If we already have the exit recorded in waitstatus, use
|
||||
it. This will happen when we detect a zombie leader,
|
||||
when we had GDB_THREAD_OPTION_EXIT enabled for it. We
|
||||
want to report its exit as TARGET_WAITKIND_THREAD_EXITED,
|
||||
as the whole process hasn't exited yet. */
|
||||
const target_waitstatus &ws = event_child->waitstatus;
|
||||
if (ws.kind () != TARGET_WAITKIND_IGNORE)
|
||||
{
|
||||
gdb_assert (ws.kind () == TARGET_WAITKIND_EXITED
|
||||
|| ws.kind () == TARGET_WAITKIND_THREAD_EXITED);
|
||||
*ourstatus = ws;
|
||||
}
|
||||
else
|
||||
ourstatus->set_exited (WEXITSTATUS (w));
|
||||
|
||||
threads_debug_printf
|
||||
("ret = %s, exited with retcode %d",
|
||||
@@ -3017,10 +3067,7 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
||||
WTERMSIG (w));
|
||||
}
|
||||
|
||||
if (ourstatus->kind () == TARGET_WAITKIND_EXITED)
|
||||
return filter_exit_event (event_child, ourstatus);
|
||||
|
||||
return ptid_of (current_thread);
|
||||
return filter_exit_event (event_child, ourstatus);
|
||||
}
|
||||
|
||||
/* If step-over executes a breakpoint instruction, in the case of a
|
||||
@@ -3527,7 +3574,8 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
||||
|
||||
/* Break the unreported fork relationship chain. */
|
||||
if (event_child->waitstatus.kind () == TARGET_WAITKIND_FORKED
|
||||
|| event_child->waitstatus.kind () == TARGET_WAITKIND_VFORKED)
|
||||
|| event_child->waitstatus.kind () == TARGET_WAITKIND_VFORKED
|
||||
|| event_child->waitstatus.kind () == TARGET_WAITKIND_THREAD_CLONED)
|
||||
{
|
||||
event_child->fork_relative->fork_relative = NULL;
|
||||
event_child->fork_relative = NULL;
|
||||
@@ -3588,10 +3636,7 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
|
||||
target_pid_to_str (ptid_of (current_thread)).c_str (),
|
||||
ourstatus->to_string ().c_str ());
|
||||
|
||||
if (ourstatus->kind () == TARGET_WAITKIND_EXITED)
|
||||
return filter_exit_event (event_child, ourstatus);
|
||||
|
||||
return ptid_of (current_thread);
|
||||
return filter_exit_event (event_child, ourstatus);
|
||||
}
|
||||
|
||||
/* Get rid of any pending event in the pipe. */
|
||||
@@ -3624,7 +3669,6 @@ linux_process_target::wait (ptid_t ptid,
|
||||
event_ptid = wait_1 (ptid, ourstatus, target_options);
|
||||
}
|
||||
while ((target_options & TARGET_WNOHANG) == 0
|
||||
&& event_ptid == null_ptid
|
||||
&& ourstatus->kind () == TARGET_WAITKIND_IGNORE);
|
||||
|
||||
/* If at least one stop was reported, there may be more. A single
|
||||
@@ -3714,8 +3758,15 @@ suspend_and_send_sigstop (thread_info *thread, lwp_info *except)
|
||||
send_sigstop (thread, except);
|
||||
}
|
||||
|
||||
/* Mark LWP dead, with WSTAT as exit status pending to report later.
|
||||
If THREAD_EVENT is true, interpret WSTAT as a thread exit event
|
||||
instead of a process exit event. This is meaningful for the leader
|
||||
thread, as we normally report a process-wide exit event when we see
|
||||
the leader exit, and a thread exit event when we see any other
|
||||
thread exit. */
|
||||
|
||||
static void
|
||||
mark_lwp_dead (struct lwp_info *lwp, int wstat)
|
||||
mark_lwp_dead (struct lwp_info *lwp, int wstat, bool thread_event)
|
||||
{
|
||||
/* Store the exit status for later. */
|
||||
lwp->status_pending_p = 1;
|
||||
@@ -3724,9 +3775,19 @@ mark_lwp_dead (struct lwp_info *lwp, int wstat)
|
||||
/* Store in waitstatus as well, as there's nothing else to process
|
||||
for this event. */
|
||||
if (WIFEXITED (wstat))
|
||||
lwp->waitstatus.set_exited (WEXITSTATUS (wstat));
|
||||
{
|
||||
if (thread_event)
|
||||
lwp->waitstatus.set_thread_exited (WEXITSTATUS (wstat));
|
||||
else
|
||||
lwp->waitstatus.set_exited (WEXITSTATUS (wstat));
|
||||
}
|
||||
else if (WIFSIGNALED (wstat))
|
||||
lwp->waitstatus.set_signalled (gdb_signal_from_host (WTERMSIG (wstat)));
|
||||
{
|
||||
gdb_assert (!thread_event);
|
||||
lwp->waitstatus.set_signalled (gdb_signal_from_host (WTERMSIG (wstat)));
|
||||
}
|
||||
else
|
||||
gdb_assert_not_reached ("unknown status kind");
|
||||
|
||||
/* Prevent trying to stop it. */
|
||||
lwp->stopped = 1;
|
||||
@@ -4263,15 +4324,14 @@ linux_set_resume_request (thread_info *thread, thread_resume *resume, size_t n)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Don't let wildcard resumes resume fork children that GDB
|
||||
does not yet know are new fork children. */
|
||||
/* Don't let wildcard resumes resume fork/vfork/clone
|
||||
children that GDB does not yet know are new children. */
|
||||
if (lwp->fork_relative != NULL)
|
||||
{
|
||||
struct lwp_info *rel = lwp->fork_relative;
|
||||
|
||||
if (rel->status_pending_p
|
||||
&& (rel->waitstatus.kind () == TARGET_WAITKIND_FORKED
|
||||
|| rel->waitstatus.kind () == TARGET_WAITKIND_VFORKED))
|
||||
&& is_new_child_status (rel->waitstatus.kind ()))
|
||||
{
|
||||
threads_debug_printf
|
||||
("not resuming LWP %ld: has queued stop reply",
|
||||
@@ -5894,6 +5954,14 @@ linux_process_target::supports_vfork_events ()
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Return the set of supported thread options. */
|
||||
|
||||
gdb_thread_options
|
||||
linux_process_target::supported_thread_options ()
|
||||
{
|
||||
return GDB_THREAD_OPTION_CLONE | GDB_THREAD_OPTION_EXIT;
|
||||
}
|
||||
|
||||
/* Check if exec events are supported. */
|
||||
|
||||
bool
|
||||
@@ -6136,6 +6204,32 @@ linux_process_target::thread_stopped (thread_info *thread)
|
||||
return get_thread_lwp (thread)->stopped;
|
||||
}
|
||||
|
||||
bool
|
||||
linux_process_target::any_resumed ()
|
||||
{
|
||||
bool any_resumed;
|
||||
|
||||
auto status_pending_p_any = [&] (thread_info *thread)
|
||||
{
|
||||
return status_pending_p_callback (thread, minus_one_ptid);
|
||||
};
|
||||
|
||||
auto not_stopped = [&] (thread_info *thread)
|
||||
{
|
||||
return not_stopped_callback (thread, minus_one_ptid);
|
||||
};
|
||||
|
||||
/* Find a resumed LWP, if any. */
|
||||
if (find_thread (status_pending_p_any) != NULL)
|
||||
any_resumed = 1;
|
||||
else if (find_thread (not_stopped) != NULL)
|
||||
any_resumed = 1;
|
||||
else
|
||||
any_resumed = 0;
|
||||
|
||||
return any_resumed;
|
||||
}
|
||||
|
||||
/* This exposes stop-all-threads functionality to other modules. */
|
||||
|
||||
void
|
||||
@@ -6921,9 +7015,10 @@ linux_process_target::thread_pending_parent (thread_info *thread)
|
||||
}
|
||||
|
||||
thread_info *
|
||||
linux_process_target::thread_pending_child (thread_info *thread)
|
||||
linux_process_target::thread_pending_child (thread_info *thread,
|
||||
target_waitkind *kind)
|
||||
{
|
||||
lwp_info *child = get_thread_lwp (thread)->pending_child ();
|
||||
lwp_info *child = get_thread_lwp (thread)->pending_child (kind);
|
||||
|
||||
if (child == nullptr)
|
||||
return nullptr;
|
||||
|
||||
@@ -234,6 +234,8 @@ public:
|
||||
|
||||
bool supports_vfork_events () override;
|
||||
|
||||
gdb_thread_options supported_thread_options () override;
|
||||
|
||||
bool supports_exec_events () override;
|
||||
|
||||
void handle_new_gdb_connection () override;
|
||||
@@ -257,6 +259,8 @@ public:
|
||||
|
||||
bool thread_stopped (thread_info *thread) override;
|
||||
|
||||
bool any_resumed () override;
|
||||
|
||||
void pause_all (bool freeze) override;
|
||||
|
||||
void unpause_all (bool unfreeze) override;
|
||||
@@ -313,7 +317,8 @@ public:
|
||||
#endif
|
||||
|
||||
thread_info *thread_pending_parent (thread_info *thread) override;
|
||||
thread_info *thread_pending_child (thread_info *thread) override;
|
||||
thread_info *thread_pending_child (thread_info *thread,
|
||||
target_waitkind *kind) override;
|
||||
|
||||
bool supports_catch_syscall () override;
|
||||
|
||||
@@ -569,13 +574,15 @@ private: /* Back to private. */
|
||||
|
||||
/* Detect zombie thread group leaders, and "exit" them. We can't
|
||||
reap their exits until all other threads in the group have
|
||||
exited. */
|
||||
void check_zombie_leaders ();
|
||||
exited. Returns true if we left any new event pending, false
|
||||
otherwise. */
|
||||
bool check_zombie_leaders ();
|
||||
|
||||
/* Convenience function that is called when the kernel reports an exit
|
||||
event. This decides whether to report the event to GDB as a
|
||||
process exit event, a thread exit event, or to suppress the
|
||||
event. */
|
||||
/* Convenience function that is called when we're about to return an
|
||||
event to the core. If the event is an exit or signalled event,
|
||||
then this decides whether to report it as process-wide event, as
|
||||
a thread exit event, or to suppress it. All other event kinds
|
||||
are passed through unmodified. */
|
||||
ptid_t filter_exit_event (lwp_info *event_child,
|
||||
target_waitstatus *ourstatus);
|
||||
|
||||
@@ -732,8 +739,8 @@ struct pending_signal
|
||||
|
||||
struct lwp_info
|
||||
{
|
||||
/* If this LWP is a fork child that wasn't reported to GDB yet, return
|
||||
its parent, else nullptr. */
|
||||
/* If this LWP is a fork/vfork/clone child that wasn't reported to
|
||||
GDB yet, return its parent, else nullptr. */
|
||||
lwp_info *pending_parent () const
|
||||
{
|
||||
if (this->fork_relative == nullptr)
|
||||
@@ -741,10 +748,10 @@ struct lwp_info
|
||||
|
||||
gdb_assert (this->fork_relative->fork_relative == this);
|
||||
|
||||
/* In a fork parent/child relationship, the parent has a status pending and
|
||||
the child does not, and a thread can only be in one such relationship
|
||||
at most. So we can recognize who is the parent based on which one has
|
||||
a pending status. */
|
||||
/* In a parent/child relationship, the parent has a status pending
|
||||
and the child does not, and a thread can only be in one such
|
||||
relationship at most. So we can recognize who is the parent
|
||||
based on which one has a pending status. */
|
||||
gdb_assert (!!this->status_pending_p
|
||||
!= !!this->fork_relative->status_pending_p);
|
||||
|
||||
@@ -754,24 +761,25 @@ struct lwp_info
|
||||
const target_waitstatus &ws
|
||||
= this->fork_relative->waitstatus;
|
||||
gdb_assert (ws.kind () == TARGET_WAITKIND_FORKED
|
||||
|| ws.kind () == TARGET_WAITKIND_VFORKED);
|
||||
|| ws.kind () == TARGET_WAITKIND_VFORKED
|
||||
|| ws.kind () == TARGET_WAITKIND_THREAD_CLONED);
|
||||
|
||||
return this->fork_relative;
|
||||
}
|
||||
|
||||
/* If this LWP is the parent of a fork child we haven't reported to GDB yet,
|
||||
return that child, else nullptr. */
|
||||
lwp_info *pending_child () const
|
||||
/* If this LWP is the parent of a fork/vfork/clone child we haven't
|
||||
reported to GDB yet, return that child, else nullptr. */
|
||||
lwp_info *pending_child (target_waitkind *kind) const
|
||||
{
|
||||
if (this->fork_relative == nullptr)
|
||||
return nullptr;
|
||||
|
||||
gdb_assert (this->fork_relative->fork_relative == this);
|
||||
|
||||
/* In a fork parent/child relationship, the parent has a status pending and
|
||||
the child does not, and a thread can only be in one such relationship
|
||||
at most. So we can recognize who is the parent based on which one has
|
||||
a pending status. */
|
||||
/* In a parent/child relationship, the parent has a status pending
|
||||
and the child does not, and a thread can only be in one such
|
||||
relationship at most. So we can recognize who is the parent
|
||||
based on which one has a pending status. */
|
||||
gdb_assert (!!this->status_pending_p
|
||||
!= !!this->fork_relative->status_pending_p);
|
||||
|
||||
@@ -780,8 +788,10 @@ struct lwp_info
|
||||
|
||||
const target_waitstatus &ws = this->waitstatus;
|
||||
gdb_assert (ws.kind () == TARGET_WAITKIND_FORKED
|
||||
|| ws.kind () == TARGET_WAITKIND_VFORKED);
|
||||
|| ws.kind () == TARGET_WAITKIND_VFORKED
|
||||
|| ws.kind () == TARGET_WAITKIND_THREAD_CLONED);
|
||||
|
||||
*kind = ws.kind ();
|
||||
return this->fork_relative;
|
||||
}
|
||||
|
||||
|
||||
@@ -1062,6 +1062,7 @@ prepare_resume_reply (char *buf, ptid_t ptid, const target_waitstatus &status)
|
||||
case TARGET_WAITKIND_FORKED:
|
||||
case TARGET_WAITKIND_VFORKED:
|
||||
case TARGET_WAITKIND_VFORK_DONE:
|
||||
case TARGET_WAITKIND_THREAD_CLONED:
|
||||
case TARGET_WAITKIND_EXECD:
|
||||
case TARGET_WAITKIND_THREAD_CREATED:
|
||||
case TARGET_WAITKIND_SYSCALL_ENTRY:
|
||||
@@ -1071,13 +1072,30 @@ prepare_resume_reply (char *buf, ptid_t ptid, const target_waitstatus &status)
|
||||
struct regcache *regcache;
|
||||
char *buf_start = buf;
|
||||
|
||||
if ((status.kind () == TARGET_WAITKIND_FORKED && cs.report_fork_events)
|
||||
if ((status.kind () == TARGET_WAITKIND_FORKED
|
||||
&& cs.report_fork_events)
|
||||
|| (status.kind () == TARGET_WAITKIND_VFORKED
|
||||
&& cs.report_vfork_events))
|
||||
&& cs.report_vfork_events)
|
||||
|| status.kind () == TARGET_WAITKIND_THREAD_CLONED)
|
||||
{
|
||||
enum gdb_signal signal = GDB_SIGNAL_TRAP;
|
||||
const char *event = (status.kind () == TARGET_WAITKIND_FORKED
|
||||
? "fork" : "vfork");
|
||||
|
||||
auto kind_remote_str = [] (target_waitkind kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case TARGET_WAITKIND_FORKED:
|
||||
return "fork";
|
||||
case TARGET_WAITKIND_VFORKED:
|
||||
return "vfork";
|
||||
case TARGET_WAITKIND_THREAD_CLONED:
|
||||
return "clone";
|
||||
default:
|
||||
gdb_assert_not_reached ("unhandled kind");
|
||||
}
|
||||
};
|
||||
|
||||
const char *event = kind_remote_str (status.kind ());
|
||||
|
||||
sprintf (buf, "T%02x%s:", signal, event);
|
||||
buf += strlen (buf);
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "dll.h"
|
||||
#include "hostio.h"
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include "gdbsupport/common-inferior.h"
|
||||
#include "gdbsupport/job-control.h"
|
||||
#include "gdbsupport/environ.h"
|
||||
@@ -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. */
|
||||
if ((vstop_event->status.kind () == TARGET_WAITKIND_FORKED
|
||||
|| vstop_event->status.kind () == TARGET_WAITKIND_VFORKED)
|
||||
|| vstop_event->status.kind () == TARGET_WAITKIND_VFORKED
|
||||
|| vstop_event->status.kind () == TARGET_WAITKIND_THREAD_CLONED)
|
||||
&& vstop_event->status.child_ptid ().matches (filter_ptid))
|
||||
return true;
|
||||
|
||||
@@ -610,6 +612,17 @@ parse_store_memtags_request (char *request, CORE_ADDR *addr, size_t *len,
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Parse thread options starting at *P and return them. On exit,
|
||||
advance *P past the options. */
|
||||
|
||||
static gdb_thread_options
|
||||
parse_gdb_thread_options (const char **p)
|
||||
{
|
||||
ULONGEST options = 0;
|
||||
*p = unpack_varlen_hex (*p, &options);
|
||||
return (gdb_thread_option) options;
|
||||
}
|
||||
|
||||
/* Handle all of the extended 'Q' packets. */
|
||||
|
||||
static void
|
||||
@@ -891,6 +904,114 @@ handle_general_set (char *own_buf)
|
||||
return;
|
||||
}
|
||||
|
||||
if (startswith (own_buf, "QThreadOptions;"))
|
||||
{
|
||||
const char *p = own_buf + strlen ("QThreadOptions");
|
||||
|
||||
gdb_thread_options supported_options = target_supported_thread_options ();
|
||||
if (supported_options == 0)
|
||||
{
|
||||
/* Something went wrong -- we don't support any option, but
|
||||
GDB sent the packet anyway. */
|
||||
write_enn (own_buf);
|
||||
return;
|
||||
}
|
||||
|
||||
/* We could store the options directly in thread->thread_options
|
||||
without this map, but that would mean that a QThreadOptions
|
||||
packet with a wildcard like "QThreadOptions;0;3:TID" would
|
||||
result in the debug logs showing:
|
||||
|
||||
[options for TID are now 0x0]
|
||||
[options for TID are now 0x3]
|
||||
|
||||
It's nicer if we only print the final options for each TID,
|
||||
and if we only print about it if the options changed compared
|
||||
to the options that were previously set on the thread. */
|
||||
std::unordered_map<thread_info *, gdb_thread_options> set_options;
|
||||
|
||||
while (*p != '\0')
|
||||
{
|
||||
if (p[0] != ';')
|
||||
{
|
||||
write_enn (own_buf);
|
||||
return;
|
||||
}
|
||||
p++;
|
||||
|
||||
/* Read the options. */
|
||||
|
||||
gdb_thread_options options = parse_gdb_thread_options (&p);
|
||||
|
||||
if ((options & ~supported_options) != 0)
|
||||
{
|
||||
/* GDB asked for an unknown or unsupported option, so
|
||||
error out. */
|
||||
std::string err
|
||||
= string_printf ("E.Unknown thread options requested: %s\n",
|
||||
to_string (options).c_str ());
|
||||
strcpy (own_buf, err.c_str ());
|
||||
return;
|
||||
}
|
||||
|
||||
ptid_t ptid;
|
||||
|
||||
if (p[0] == ';' || p[0] == '\0')
|
||||
ptid = minus_one_ptid;
|
||||
else if (p[0] == ':')
|
||||
{
|
||||
const char *q;
|
||||
|
||||
ptid = read_ptid (p + 1, &q);
|
||||
|
||||
if (p == q)
|
||||
{
|
||||
write_enn (own_buf);
|
||||
return;
|
||||
}
|
||||
p = q;
|
||||
if (p[0] != ';' && p[0] != '\0')
|
||||
{
|
||||
write_enn (own_buf);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
write_enn (own_buf);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Convert PID.-1 => PID.0 for ptid.matches. */
|
||||
if (ptid.lwp () == -1)
|
||||
ptid = ptid_t (ptid.pid ());
|
||||
|
||||
for_each_thread ([&] (thread_info *thread)
|
||||
{
|
||||
if (ptid_of (thread).matches (ptid))
|
||||
set_options[thread] = options;
|
||||
});
|
||||
}
|
||||
|
||||
for (const auto &iter : set_options)
|
||||
{
|
||||
thread_info *thread = iter.first;
|
||||
gdb_thread_options options = iter.second;
|
||||
|
||||
if (thread->thread_options != options)
|
||||
{
|
||||
threads_debug_printf ("[options for %s are now %s]\n",
|
||||
target_pid_to_str (ptid_of (thread)).c_str (),
|
||||
to_string (options).c_str ());
|
||||
|
||||
thread->thread_options = options;
|
||||
}
|
||||
}
|
||||
|
||||
write_ok (own_buf);
|
||||
return;
|
||||
}
|
||||
|
||||
if (startswith (own_buf, "QStartupWithShell:"))
|
||||
{
|
||||
const char *value = own_buf + strlen ("QStartupWithShell:");
|
||||
@@ -1223,8 +1344,9 @@ handle_detach (char *own_buf)
|
||||
continue;
|
||||
|
||||
/* Only threads that have a pending fork event. */
|
||||
thread_info *child = target_thread_pending_child (thread);
|
||||
if (child == nullptr)
|
||||
target_waitkind kind;
|
||||
thread_info *child = target_thread_pending_child (thread, &kind);
|
||||
if (child == nullptr || kind == TARGET_WAITKIND_THREAD_CLONED)
|
||||
continue;
|
||||
|
||||
process_info *fork_child_process = get_thread_process (child);
|
||||
@@ -1644,9 +1766,10 @@ handle_qxfer_threads_worker (thread_info *thread, struct buffer *buffer)
|
||||
gdb_byte *handle;
|
||||
bool handle_status = target_thread_handle (ptid, &handle, &handle_len);
|
||||
|
||||
/* If this is a fork or vfork child (has a fork parent), GDB does not yet
|
||||
know about this process, and must not know about it until it gets the
|
||||
corresponding (v)fork event. Exclude this thread from the list. */
|
||||
/* If this is a (v)fork/clone child (has a (v)fork/clone parent),
|
||||
GDB does not yet know about this thread, and must not know about
|
||||
it until it gets the corresponding (v)fork/clone event. Exclude
|
||||
this thread from the list. */
|
||||
if (target_thread_pending_parent (thread) != nullptr)
|
||||
return;
|
||||
|
||||
@@ -2363,6 +2486,8 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
|
||||
cs.vCont_supported = 1;
|
||||
else if (feature == "QThreadEvents+")
|
||||
;
|
||||
else if (feature == "QThreadOptions+")
|
||||
;
|
||||
else if (feature == "no-resumed+")
|
||||
{
|
||||
/* GDB supports and wants TARGET_WAITKIND_NO_RESUMED
|
||||
@@ -2489,6 +2614,14 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
|
||||
|
||||
strcat (own_buf, ";vContSupported+");
|
||||
|
||||
gdb_thread_options supported_options = target_supported_thread_options ();
|
||||
if (supported_options != 0)
|
||||
{
|
||||
char *end_buf = own_buf + strlen (own_buf);
|
||||
sprintf (end_buf, ";QThreadOptions=%s",
|
||||
phex_nz (supported_options, sizeof (supported_options)));
|
||||
}
|
||||
|
||||
strcat (own_buf, ";QThreadEvents+");
|
||||
|
||||
strcat (own_buf, ";no-resumed+");
|
||||
@@ -2928,6 +3061,7 @@ resume (struct thread_resume *actions, size_t num_actions)
|
||||
|
||||
if (cs.last_status.kind () != TARGET_WAITKIND_EXITED
|
||||
&& cs.last_status.kind () != TARGET_WAITKIND_SIGNALLED
|
||||
&& cs.last_status.kind () != TARGET_WAITKIND_THREAD_EXITED
|
||||
&& cs.last_status.kind () != TARGET_WAITKIND_NO_RESUMED)
|
||||
current_thread->last_status = cs.last_status;
|
||||
|
||||
@@ -4606,7 +4740,17 @@ handle_target_event (int err, gdb_client_data client_data)
|
||||
}
|
||||
}
|
||||
else
|
||||
push_stop_notification (cs.last_ptid, cs.last_status);
|
||||
{
|
||||
push_stop_notification (cs.last_ptid, cs.last_status);
|
||||
|
||||
if (cs.last_status.kind () == TARGET_WAITKIND_THREAD_EXITED
|
||||
&& !target_any_resumed ())
|
||||
{
|
||||
target_waitstatus ws;
|
||||
ws.set_no_resumed ();
|
||||
push_stop_notification (null_ptid, ws);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Be sure to not change the selected thread behind GDB's back.
|
||||
|
||||
@@ -530,6 +530,12 @@ process_stratum_target::supports_vfork_events ()
|
||||
return false;
|
||||
}
|
||||
|
||||
gdb_thread_options
|
||||
process_stratum_target::supported_thread_options ()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
process_stratum_target::supports_exec_events ()
|
||||
{
|
||||
@@ -606,6 +612,12 @@ process_stratum_target::thread_stopped (thread_info *thread)
|
||||
gdb_assert_not_reached ("target op thread_stopped not supported");
|
||||
}
|
||||
|
||||
bool
|
||||
process_stratum_target::any_resumed ()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
process_stratum_target::supports_get_tib_address ()
|
||||
{
|
||||
@@ -808,7 +820,8 @@ process_stratum_target::thread_pending_parent (thread_info *thread)
|
||||
}
|
||||
|
||||
thread_info *
|
||||
process_stratum_target::thread_pending_child (thread_info *thread)
|
||||
process_stratum_target::thread_pending_child (thread_info *thread,
|
||||
target_waitkind *kind)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -277,6 +277,9 @@ public:
|
||||
/* Returns true if vfork events are supported. */
|
||||
virtual bool supports_vfork_events ();
|
||||
|
||||
/* Returns the set of supported thread options. */
|
||||
virtual gdb_thread_options supported_thread_options ();
|
||||
|
||||
/* Returns true if exec events are supported. */
|
||||
virtual bool supports_exec_events ();
|
||||
|
||||
@@ -317,6 +320,9 @@ public:
|
||||
/* Return true if THREAD is known to be stopped now. */
|
||||
virtual bool thread_stopped (thread_info *thread);
|
||||
|
||||
/* Return true if any thread is known to be resumed. */
|
||||
virtual bool any_resumed ();
|
||||
|
||||
/* Return true if the get_tib_address op is supported. */
|
||||
virtual bool supports_get_tib_address ();
|
||||
|
||||
@@ -476,13 +482,14 @@ public:
|
||||
virtual bool thread_handle (ptid_t ptid, gdb_byte **handle,
|
||||
int *handle_len);
|
||||
|
||||
/* If THREAD is a fork child that was not reported to GDB, return its parent
|
||||
else nullptr. */
|
||||
/* If THREAD is a fork/vfork/clone child that was not reported to
|
||||
GDB, return its parent else nullptr. */
|
||||
virtual thread_info *thread_pending_parent (thread_info *thread);
|
||||
|
||||
/* If THREAD is the parent of a fork child that was not reported to GDB,
|
||||
return this child, else nullptr. */
|
||||
virtual thread_info *thread_pending_child (thread_info *thread);
|
||||
/* If THREAD is the parent of a fork/vfork/clone child that was not
|
||||
reported to GDB, return this child, else nullptr. */
|
||||
virtual thread_info *thread_pending_child (thread_info *thread,
|
||||
target_waitkind *kind);
|
||||
|
||||
/* Returns true if the target can software single step. */
|
||||
virtual bool supports_software_single_step ();
|
||||
@@ -532,6 +539,9 @@ int kill_inferior (process_info *proc);
|
||||
#define target_supports_vfork_events() \
|
||||
the_target->supports_vfork_events ()
|
||||
|
||||
#define target_supported_thread_options(options) \
|
||||
the_target->supported_thread_options (options)
|
||||
|
||||
#define target_supports_exec_events() \
|
||||
the_target->supports_exec_events ()
|
||||
|
||||
@@ -676,6 +686,9 @@ target_read_btrace_conf (struct btrace_target_info *tinfo,
|
||||
#define target_supports_software_single_step() \
|
||||
the_target->supports_software_single_step ()
|
||||
|
||||
#define target_any_resumed() \
|
||||
the_target->any_resumed ()
|
||||
|
||||
ptid_t mywait (ptid_t ptid, struct target_waitstatus *ourstatus,
|
||||
target_wait_flags options, int connected_wait);
|
||||
|
||||
@@ -695,9 +708,9 @@ target_thread_pending_parent (thread_info *thread)
|
||||
}
|
||||
|
||||
static inline thread_info *
|
||||
target_thread_pending_child (thread_info *thread)
|
||||
target_thread_pending_child (thread_info *thread, target_waitkind *kind)
|
||||
{
|
||||
return the_target->thread_pending_child (thread);
|
||||
return the_target->thread_pending_child (thread, kind);
|
||||
}
|
||||
|
||||
int read_inferior_memory (CORE_ADDR memaddr, unsigned char *myaddr, int len);
|
||||
|
||||
@@ -130,6 +130,17 @@ public:
|
||||
typedef E enum_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:
|
||||
/* Allow default construction. */
|
||||
constexpr enum_flags ()
|
||||
@@ -183,6 +194,18 @@ public:
|
||||
/* Binary operations involving some unrelated type (which would be a
|
||||
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:
|
||||
/* Stored as enum_type because GDB knows to print the 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>>
|
||||
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 */
|
||||
|
||||
/* In C, the flags type is just a typedef for the enum type. */
|
||||
|
||||
Reference in New Issue
Block a user