static void wait_for_inferior (inferior *inf);
+static void restart_threads (struct thread_info *event_thread,
+ inferior *inf = nullptr);
+
+static bool start_step_over (void);
+
+static bool step_over_info_valid_p (void);
+
/* Asynchronous signal handler registered as event loop source for
when we have pending events ready to be passed to the core. */
static struct async_event_handler *infrun_async_inferior_event_token;
inferior *parent_inf = current_inferior ();
inferior *child_inf = nullptr;
+ gdb_assert (parent_inf->thread_waiting_for_vfork_done == nullptr);
+
if (!follow_child)
{
/* Detach new forked process? */
}
else
{
- child_inf->aspace = new_address_space ();
+ child_inf->aspace = new address_space ();
child_inf->pspace = new program_space (child_inf->aspace);
child_inf->removable = 1;
clone_program_space (child_inf->pspace, parent_inf->pspace);
insert breakpoints, so that we can debug it. A
subsequent child exec or exit is enough to know when does
the child stops using the parent's address space. */
- parent_inf->waiting_for_vfork_done = detach_fork;
+ parent_inf->thread_waiting_for_vfork_done
+ = detach_fork ? inferior_thread () : nullptr;
parent_inf->pspace->breakpoints_not_allowed = detach_fork;
}
}
child_inf->aspace = parent_inf->aspace;
child_inf->pspace = parent_inf->pspace;
- parent_inf->aspace = new_address_space ();
+ parent_inf->aspace = new address_space ();
parent_inf->pspace = new program_space (parent_inf->aspace);
clone_program_space (parent_inf->pspace, child_inf->pspace);
}
else
{
- child_inf->aspace = new_address_space ();
+ child_inf->aspace = new address_space ();
child_inf->pspace = new program_space (child_inf->aspace);
child_inf->removable = 1;
child_inf->symfile_flags = SYMFILE_NO_READ;
child_inf->pending_detach = 0;
parent_inf->vfork_child = child_inf;
parent_inf->pending_detach = detach_fork;
- parent_inf->waiting_for_vfork_done = 0;
}
else if (detach_fork)
{
parent = inferior_ptid;
child = tp->pending_follow.child_ptid ();
+ /* If handling a vfork, stop all the inferior's threads, they will be
+ restarted when the vfork shared region is complete. */
+ if (tp->pending_follow.kind () == TARGET_WAITKIND_VFORKED
+ && target_is_non_stop_p ())
+ stop_all_threads ("handling vfork", tp->inf);
+
process_stratum_target *parent_targ = tp->inf->process_target ();
/* Set up inferior(s) as specified by the caller, and tell the
target to do whatever is necessary to follow either parent
}
}
+/* Handle TARGET_WAITKIND_VFORK_DONE. */
+
+static void
+handle_vfork_done (thread_info *event_thread)
+{
+ /* We only care about this event if inferior::thread_waiting_for_vfork_done is
+ set, that is if we are waiting for a vfork child not under our control
+ (because we detached it) to exec or exit.
+
+ If an inferior has vforked and we are debugging the child, we don't use
+ the vfork-done event to get notified about the end of the shared address
+ space window. We rely instead on the child's exec or exit event, and the
+ inferior::vfork_{parent,child} fields are used instead. See
+ handle_vfork_child_exec_or_exit for that. */
+ if (event_thread->inf->thread_waiting_for_vfork_done == nullptr)
+ {
+ infrun_debug_printf ("not waiting for a vfork-done event");
+ return;
+ }
+
+ INFRUN_SCOPED_DEBUG_ENTER_EXIT;
+
+ /* We stopped all threads (other than the vforking thread) of the inferior in
+ follow_fork and kept them stopped until now. It should therefore not be
+ possible for another thread to have reported a vfork during that window.
+ If THREAD_WAITING_FOR_VFORK_DONE is set, it has to be the same thread whose
+ vfork-done we are handling right now. */
+ gdb_assert (event_thread->inf->thread_waiting_for_vfork_done == event_thread);
+
+ event_thread->inf->thread_waiting_for_vfork_done = nullptr;
+ event_thread->inf->pspace->breakpoints_not_allowed = 0;
+
+ /* On non-stop targets, we stopped all the inferior's threads in follow_fork,
+ resume them now. On all-stop targets, everything that needs to be resumed
+ will be when we resume the event thread. */
+ if (target_is_non_stop_p ())
+ {
+ /* restart_threads and start_step_over may change the current thread, make
+ sure we leave the event thread as the current thread. */
+ scoped_restore_current_thread restore_thread;
+
+ insert_breakpoints ();
+ start_step_over ();
+
+ if (!step_over_info_valid_p ())
+ restart_threads (event_thread, event_thread->inf);
+ }
+}
+
/* Enum strings for "set|show follow-exec-mode". */
static const char follow_exec_mode_new[] = "new";
infrun_inferior_exit (struct inferior *inf)
{
inf->displaced_step_state.reset ();
+ inf->thread_waiting_for_vfork_done = nullptr;
}
static void
one in progress at the time of the exec, it must have been the exec'ing
thread. */
clear_step_over_info ();
+
+ inf->thread_waiting_for_vfork_done = nullptr;
}
/* If ON, and the architecture supports it, GDB will use displaced
continue;
}
+ if (tp->inf->thread_waiting_for_vfork_done != nullptr)
+ {
+ /* When we stop all threads, handling a vfork, any thread in the step
+ over chain remains there. A user could also try to continue a
+ thread stopped at a breakpoint while another thread is waiting for
+ a vfork-done event. In any case, we don't want to start a step
+ over right now. */
+ continue;
+ }
+
/* Remove thread from the THREADS_TO_STEP chain. If anything goes wrong
while we try to prepare the displaced step, we don't add it back to
the global step over chain. This is to avoid a thread staying in the
return a wildcard ptid. */
if (target_is_non_stop_p ())
return inferior_ptid;
- else
- return user_visible_resume_ptid (user_step);
+
+ /* The rest of the function assumes non-stop==off and
+ target-non-stop==off.
+
+ If a thread is waiting for a vfork-done event, it means breakpoints are out
+ for this inferior (well, program space in fact). We don't want to resume
+ any thread other than the one waiting for vfork done, otherwise these other
+ threads could miss breakpoints. So if a thread in the resumption set is
+ waiting for a vfork-done event, resume only that thread.
+
+ The resumption set width depends on whether schedule-multiple is on or off.
+
+ Note that if the target_resume interface was more flexible, we could be
+ smarter here when schedule-multiple is on. For example, imagine 3
+ inferiors with 2 threads each (1.1, 1.2, 2.1, 2.2, 3.1 and 3.2). Threads
+ 2.1 and 3.2 are both waiting for a vfork-done event. Then we could ask the
+ target(s) to resume:
+
+ - All threads of inferior 1
+ - Thread 2.1
+ - Thread 3.2
+
+ Since we don't have that flexibility (we can only pass one ptid), just
+ resume the first thread waiting for a vfork-done event we find (e.g. thread
+ 2.1). */
+ if (sched_multi)
+ {
+ for (inferior *inf : all_non_exited_inferiors ())
+ if (inf->thread_waiting_for_vfork_done != nullptr)
+ return inf->thread_waiting_for_vfork_done->ptid;
+ }
+ else if (current_inferior ()->thread_waiting_for_vfork_done != nullptr)
+ return current_inferior ()->thread_waiting_for_vfork_done->ptid;
+
+ return user_visible_resume_ptid (user_step);
}
/* Wrapper for target_resume, that handles infrun-specific
else
target_pass_signals (signal_pass);
+ infrun_debug_printf ("resume_ptid=%s, step=%d, sig=%s",
+ resume_ptid.to_string ().c_str (),
+ step, gdb_signal_to_symbol_string (sig));
+
target_resume (resume_ptid, step, sig);
}
if (target_can_async_p ())
{
- target_async (1);
+ target_async (true);
/* Tell the event loop we have an event to process. */
mark_async_event_handler (infrun_async_inferior_event_token);
}
/* Depends on stepped_breakpoint. */
step = currently_stepping (tp);
- if (current_inferior ()->waiting_for_vfork_done)
+ if (current_inferior ()->thread_waiting_for_vfork_done != nullptr)
{
/* Don't try to single-step a vfork parent that is waiting for
the child to get out of the shared memory region (by exec'ing
&& use_displaced_stepping (tp)
&& !step_over_info_valid_p ()
&& sig == GDB_SIGNAL_0
- && !current_inferior ()->waiting_for_vfork_done)
+ && current_inferior ()->thread_waiting_for_vfork_done == nullptr)
{
displaced_step_prepare_status prepare_status
= displaced_step_prepare (tp);
/* Fallback to stepping over the breakpoint in-line. */
if (target_is_non_stop_p ())
- stop_all_threads ();
+ stop_all_threads ("displaced stepping falling back on inline stepping");
set_step_over_info (regcache->aspace (),
regcache_read_pc (regcache), 0, tp->global_num);
continue;
}
+ /* If a thread of that inferior is waiting for a vfork-done
+ (for a detached vfork child to exec or exit), breakpoints are
+ removed. We must not resume any thread of that inferior, other
+ than the one waiting for the vfork-done. */
+ if (tp->inf->thread_waiting_for_vfork_done != nullptr
+ && tp != tp->inf->thread_waiting_for_vfork_done)
+ {
+ infrun_debug_printf ("[%s] another thread of this inferior is "
+ "waiting for vfork-done",
+ tp->ptid.to_string ().c_str ());
+ continue;
+ }
+
infrun_debug_printf ("resuming %s",
tp->ptid.to_string ().c_str ());
error (_("Command aborted."));
}
}
- else if (!cur_thr->resumed () && !thread_is_in_step_over_chain (cur_thr))
+ else if (!cur_thr->resumed ()
+ && !thread_is_in_step_over_chain (cur_thr)
+ /* In non-stop, forbid resuming a thread if some other thread of
+ that inferior is waiting for a vfork-done event (this means
+ breakpoints are out for this inferior). */
+ && !(non_stop
+ && cur_thr->inf->thread_waiting_for_vfork_done != nullptr))
{
/* The thread wasn't started, and isn't queued, run it now. */
reset_ecs (ecs, cur_thr);
};
static bool handle_one (const wait_one_event &event);
-static void restart_threads (struct thread_info *event_thread);
/* Prepare and stabilize the inferior for detaching it. E.g.,
detaching while a thread is displaced stepping is a recipe for
}
}
+/* If all-stop, but there exists a non-stop target, stop all threads
+ now that we're presenting the stop to the user. */
+
+static void
+stop_all_threads_if_all_stop_mode ()
+{
+ if (!non_stop && exists_non_stop_target ())
+ stop_all_threads ("presenting stop to user in all-stop");
+}
+
/* Wait for control to return from inferior to debugger.
If inferior gets a signal, we may decide to start it up again
break;
}
+ stop_all_threads_if_all_stop_mode ();
+
/* No error, don't finish the state yet. */
finish_state.release ();
}
static void
clean_up_just_stopped_threads_fsms (struct execution_control_state *ecs)
{
+ /* The first clean_up call below assumes the event thread is the current
+ one. */
+ if (ecs->event_thread != nullptr)
+ gdb_assert (ecs->event_thread == inferior_thread ());
+
if (ecs->event_thread != nullptr
&& ecs->event_thread->thread_fsm () != nullptr)
ecs->event_thread->thread_fsm ()->clean_up (ecs->event_thread);
if (!non_stop)
{
+ scoped_restore_current_thread restore_thread;
+
for (thread_info *thr : all_non_exited_threads ())
{
if (thr->thread_fsm () == nullptr)
switch_to_thread (thr);
thr->thread_fsm ()->clean_up (thr);
}
-
- if (ecs->event_thread != nullptr)
- switch_to_thread (ecs->event_thread);
}
}
{
target_terminal::ours ();
gdb::observers::sync_execution_done.notify ();
- ui_register_input_event_handler (ui);
+ ui->register_file_handler ();
}
}
bool should_notify_stop = true;
int proceeded = 0;
+ stop_all_threads_if_all_stop_mode ();
+
clean_up_just_stopped_threads_fsms (ecs);
if (thr != nullptr && thr->thread_fsm () != nullptr)
reinstalled here. */
}
+ /* Handling this event might have caused some inferiors to become prunable.
+ For example, the exit of an inferior that was automatically added. Try
+ to get rid of them. Keeping those around slows down things linearly.
+
+ Note that this never removes the current inferior. Therefore, call this
+ after RESTORE_THREAD went out of scope, in case the event inferior (which was
+ temporarily made the current inferior) is meant to be deleted.
+
+ Call this before all_uis_check_sync_execution_done, so that notifications about
+ removed inferiors appear before the prompt. */
+ prune_inferiors ();
+
/* If a UI was in sync execution mode, and now isn't, restore its
prompt (a synchronous execution command has finished, and we're
ready for input). */
stop_func_start is NOT advanced when in a range of a
non-contiguous block that does not contain the entry pc. */
if (block != nullptr
- && ecs->stop_func_start <= BLOCK_ENTRY_PC (block)
- && BLOCK_ENTRY_PC (block) < ecs->stop_func_end)
+ && ecs->stop_func_start <= block->entry_pc ()
+ && block->entry_pc () < ecs->stop_func_end)
{
ecs->stop_func_start
+= gdbarch_deprecated_function_start_offset (gdbarch);
{
/* If nothing is resumed, remove the target from the
event loop. */
- target_async (0);
+ target_async (false);
}
else if (event.ws.kind () != TARGET_WAITKIND_IGNORE)
return event;
/* See infrun.h. */
void
-stop_all_threads (void)
+stop_all_threads (const char *reason, inferior *inf)
{
/* We may need multiple passes to discover all threads. */
int pass;
gdb_assert (exists_non_stop_target ());
- infrun_debug_printf ("starting");
+ INFRUN_SCOPED_DEBUG_START_END ("reason=%s, inf=%d", reason,
+ inf != nullptr ? inf->num : -1);
+
+ infrun_debug_show_threads ("non-exited threads",
+ all_non_exited_threads ());
scoped_restore_current_thread restore_thread;
- /* Enable thread events of all targets. */
+ /* Enable thread events on relevant targets. */
for (auto *target : all_non_exited_process_targets ())
{
+ if (inf != nullptr && inf->process_target () != target)
+ continue;
+
switch_to_target_no_thread (target);
target_thread_events (true);
}
SCOPE_EXIT
{
- /* Disable thread events of all targets. */
+ /* Disable thread events on relevant targets. */
for (auto *target : all_non_exited_process_targets ())
{
+ if (inf != nullptr && inf->process_target () != target)
+ continue;
+
switch_to_target_no_thread (target);
target_thread_events (false);
}
for (auto *target : all_non_exited_process_targets ())
{
+ if (inf != nullptr && inf->process_target () != target)
+ continue;
+
switch_to_target_no_thread (target);
update_thread_list ();
}
to tell the target to stop. */
for (thread_info *t : all_non_exited_threads ())
{
+ if (inf != nullptr && t->inf != inf)
+ continue;
+
/* For a single-target setting with an all-stop target,
we would not even arrive here. For a multi-target
setting, until GDB is able to handle a mixture of
inferior *curr_inf = current_inferior ();
scoped_restore_current_thread restore_thread;
-
- for (auto *target : all_non_exited_process_targets ())
- {
- switch_to_target_no_thread (target);
- update_thread_list ();
- }
+ update_thread_list ();
/* If:
ecs->ptid = inferior_ptid;
if (should_resume)
- keep_going (ecs);
+ {
+ /* Never call switch_back_to_stepped_thread if we are waiting for
+ vfork-done (waiting for an external vfork child to exec or
+ exit). We will resume only the vforking thread for the purpose
+ of collecting the vfork-done event, and we will restart any
+ step once the critical shared address space window is done. */
+ if ((!follow_child
+ && detach_fork
+ && parent->inf->thread_waiting_for_vfork_done != nullptr)
+ || !switch_back_to_stepped_thread (ecs))
+ keep_going (ecs);
+ }
else
stop_waiting (ecs);
return;
context_switch (ecs);
- current_inferior ()->waiting_for_vfork_done = 0;
- current_inferior ()->pspace->breakpoints_not_allowed = 0;
+ handle_vfork_done (ecs->event_thread);
+ gdb_assert (inferior_thread () == ecs->event_thread);
if (handle_stop_requested (ecs))
return;
- /* This also takes care of reinserting breakpoints in the
- previously locked inferior. */
- keep_going (ecs);
+ if (!switch_back_to_stepped_thread (ecs))
+ {
+ gdb_assert (inferior_thread () == ecs->event_thread);
+ /* This also takes care of reinserting breakpoints in the
+ previously locked inferior. */
+ keep_going (ecs);
+ }
return;
case TARGET_WAITKIND_EXECD:
}
/* Restart threads back to what they were trying to do back when we
- paused them for an in-line step-over. The EVENT_THREAD thread is
- ignored. */
+ paused them (because of an in-line step-over or vfork, for example).
+ The EVENT_THREAD thread is ignored (not restarted).
+
+ If INF is non-nullptr, only resume threads from INF. */
static void
-restart_threads (struct thread_info *event_thread)
+restart_threads (struct thread_info *event_thread, inferior *inf)
{
+ INFRUN_SCOPED_DEBUG_START_END ("event_thread=%s, inf=%d",
+ event_thread->ptid.to_string ().c_str (),
+ inf != nullptr ? inf->num : -1);
+
+ gdb_assert (!step_over_info_valid_p ());
+
/* In case the instruction just stepped spawned a new thread. */
update_thread_list ();
for (thread_info *tp : all_non_exited_threads ())
{
+ if (inf != nullptr && tp->inf != inf)
+ continue;
+
if (tp->inf->detaching)
{
infrun_debug_printf ("restart threads: [%s] inferior detaching",
cases such as throwing an exception from inside a signal
handler. */
- b = SYMBOL_BLOCK_VALUE (func);
+ b = func->value_block ();
ALL_BLOCK_SYMBOLS (b, iter, sym)
{
if (!sym->is_argument ())
/* Let callers know we don't want to wait for the inferior anymore. */
ecs->wait_some_more = 0;
-
- /* If all-stop, but there exists a non-stop target, stop all
- threads now that we're presenting the stop to the user. */
- if (!non_stop && exists_non_stop_target ())
- stop_all_threads ();
}
/* Like keep_going, but passes the signal to the inferior, even if the
we're about to step over, otherwise other threads could miss
it. */
if (step_over_info_valid_p () && target_is_non_stop_p ())
- stop_all_threads ();
+ stop_all_threads ("starting in-line step-over");
/* Stop stepping if inserting breakpoints fails. */
try
/* Look up the hook_stop and run it (CLI internally handles problem
of stop_command's pre-hook not existing). */
- if (stop_command != NULL)
- {
- stop_context saved_context;
+ stop_context saved_context;
- try
- {
- execute_cmd_pre_hook (stop_command);
- }
- catch (const gdb_exception &ex)
- {
- exception_fprintf (gdb_stderr, ex,
- "Error while running hook_stop:\n");
- }
-
- /* If the stop hook resumes the target, then there's no point in
- trying to notify about the previous stop; its context is
- gone. Likewise if the command switches thread or inferior --
- the observers would print a stop for the wrong
- thread/inferior. */
- if (saved_context.changed ())
- return 1;
+ try
+ {
+ execute_cmd_pre_hook (stop_command);
+ }
+ catch (const gdb_exception &ex)
+ {
+ exception_fprintf (gdb_stderr, ex,
+ "Error while running hook_stop:\n");
}
+ /* If the stop hook resumes the target, then there's no point in
+ trying to notify about the previous stop; its context is
+ gone. Likewise if the command switches thread or inferior --
+ the observers would print a stop for the wrong
+ thread/inferior. */
+ if (saved_context.changed ())
+ return 1;
+
/* Notify observers about the stop. This is where the interpreters
print the stop event. */
if (inferior_ptid != null_ptid)
breakpoint_auto_delete (inferior_thread ()->control.stop_bpstat);
}
- /* Try to get rid of automatically added inferiors that are no
- longer needed. Keeping those around slows down things linearly.
- Note that this never removes the current inferior. */
- prune_inferiors ();
-
return 0;
}
\f
all signals cumulatively specified."));
set_cmd_completer (c, handle_completer);
- if (!dbx_commands)
- stop_command = add_cmd ("stop", class_obscure,
- not_just_help_class_command, _("\
+ stop_command = add_cmd ("stop", class_obscure,
+ not_just_help_class_command, _("\
There is no `stop' command, but you can set a hook on `stop'.\n\
This allows you to set a list of commands to be run each time execution\n\
of the program stops."), &cmdlist);