aboutsummaryrefslogtreecommitdiff
path: root/gdb/linux-fork.c
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/linux-fork.c')
-rw-r--r--gdb/linux-fork.c674
1 files changed, 494 insertions, 180 deletions
diff --git a/gdb/linux-fork.c b/gdb/linux-fork.c
index c457a90..9e986d8 100644
--- a/gdb/linux-fork.c
+++ b/gdb/linux-fork.c
@@ -1,6 +1,6 @@
/* GNU/Linux native-dependent code for debugging multiple forks.
- Copyright (C) 2005-2024 Free Software Foundation, Inc.
+ Copyright (C) 2005-2025 Free Software Foundation, Inc.
This file is part of GDB.
@@ -29,9 +29,12 @@
#include "linux-nat.h"
#include "gdbthread.h"
#include "source.h"
+#include "progspace-and-thread.h"
+#include "cli/cli-style.h"
#include "nat/gdb_ptrace.h"
#include "gdbsupport/gdb_wait.h"
+#include "gdbsupport/eintr.h"
#include "target/waitstatus.h"
#include <dirent.h>
#include <ctype.h>
@@ -39,10 +42,11 @@
#include <list>
/* Fork list data structure: */
+
struct fork_info
{
- explicit fork_info (pid_t pid)
- : ptid (pid, pid)
+ explicit fork_info (pid_t pid, int fork_num)
+ : ptid (pid, pid), num (fork_num)
{
}
@@ -70,7 +74,7 @@ struct fork_info
ptid_t parent_ptid = null_ptid;
/* Convenient handle (GDB fork id). */
- int num = 0;
+ int num;
/* Convenient for info fork, saves having to actually switch
contexts. */
@@ -84,115 +88,247 @@ struct fork_info
int maxfd = 0;
};
-static std::list<fork_info> fork_list;
-static int highest_fork_num;
+/* Per-inferior checkpoint data. */
+
+struct checkpoint_inferior_data
+{
+ /* List of forks (checkpoints) in particular inferior. Once a
+ checkpoint has been created, fork_list will contain at least two
+ items, the first in the list will be the original (or, if not
+ original, then the oldest) fork. */
+ std::list<fork_info> fork_list;
+
+ /* Most recently assigned fork number; when 0, no checkpoints have
+ been created yet. */
+ int highest_fork_num = 0;
+};
+
+/* Per-inferior data key. */
+
+static const registry<inferior>::key<checkpoint_inferior_data>
+ checkpoint_inferior_data_key;
+
+/* Fetch per-inferior checkpoint data. It always returns a valid pointer
+ to a checkpoint_inferior_info struct. */
+
+static struct checkpoint_inferior_data *
+get_checkpoint_inferior_data (struct inferior *inf)
+{
+ struct checkpoint_inferior_data *data;
+
+ data = checkpoint_inferior_data_key.get (inf);
+ if (data == nullptr)
+ data = checkpoint_inferior_data_key.emplace (inf);
+
+ return data;
+}
+
+/* Return a reference to the per-inferior fork list. */
+
+static std::list<fork_info> &
+fork_list (inferior *inf)
+{
+ return get_checkpoint_inferior_data (inf)->fork_list;
+}
+
+/* Increment the highest fork number for inferior INF, returning
+ the new value. */
+
+static int
+increment_highest_fork_num (inferior *inf)
+{
+ return ++get_checkpoint_inferior_data (inf)->highest_fork_num;
+}
+
+/* Reset the highest fork number for inferior INF. */
+
+static void
+reset_highest_fork_num (inferior *inf)
+{
+ get_checkpoint_inferior_data (inf)->highest_fork_num = 0;
+}
/* Fork list methods: */
-int
-forks_exist_p (void)
+/* Predicate which returns true if checkpoint(s) exist in the inferior
+ INF, false otherwise. */
+
+bool
+forks_exist_p (inferior *inf)
{
- return !fork_list.empty ();
+ /* Avoid allocating checkpoint_inferior_data storage by checking
+ to see if such storage exists prior to calling fork_list.
+ If we just call fork_list alone, then that call will create
+ this storage, even for inferiors which don't need it. */
+ return (checkpoint_inferior_data_key.get (inf) != nullptr
+ && !fork_list (inf).empty ());
}
-/* Return the last fork in the list. */
+/* Return the last fork in the list for inferior INF. */
static struct fork_info *
-find_last_fork (void)
+find_last_fork (inferior *inf)
{
+ auto &fork_list = ::fork_list (inf);
+
if (fork_list.empty ())
return NULL;
return &fork_list.back ();
}
-/* Return true iff there's one fork in the list. */
+/* Return true iff there's one fork in the list for inferior INF. */
static bool
-one_fork_p ()
+one_fork_p (inferior *inf)
{
- return fork_list.size () == 1;
+ return fork_list (inf).size () == 1;
}
/* Add a new fork to the internal fork list. */
void
-add_fork (pid_t pid)
+add_fork (pid_t pid, inferior *inf)
{
- fork_list.emplace_back (pid);
-
- if (one_fork_p ())
- highest_fork_num = 0;
-
- fork_info *fp = &fork_list.back ();
- fp->num = ++highest_fork_num;
+ fork_list (inf).emplace_back (pid, increment_highest_fork_num (inf));
}
+/* Delete a fork for PTID in inferior INF. When the last fork is
+ deleted, HIGHEST_FORK_NUM for the given inferior is reset to 0.
+ The fork list may also be made to be empty when only one fork
+ remains. */
+
static void
-delete_fork (ptid_t ptid)
+delete_fork (ptid_t ptid, inferior *inf)
{
linux_target->low_forget_process (ptid.pid ());
+ auto &fork_list = ::fork_list (inf);
for (auto it = fork_list.begin (); it != fork_list.end (); ++it)
if (it->ptid == ptid)
{
fork_list.erase (it);
+ if (fork_list.empty ())
+ reset_highest_fork_num (inf);
+
/* Special case: if there is now only one process in the list,
and if it is (hopefully!) the current inferior_ptid, then
remove it, leaving the list empty -- we're now down to the
default case of debugging a single process. */
- if (one_fork_p () && fork_list.front ().ptid == inferior_ptid)
+ if (one_fork_p (inf) && fork_list.front ().ptid == inferior_ptid)
{
/* Last fork -- delete from list and handle as solo
process (should be a safe recursion). */
- delete_fork (inferior_ptid);
+ delete_fork (inferior_ptid, inf);
}
return;
}
}
-/* Find a fork_info by matching PTID. */
-static struct fork_info *
+/* Find a fork_info and inferior by matching PTID. */
+
+static std::pair<fork_info *, inferior *>
find_fork_ptid (ptid_t ptid)
{
- for (fork_info &fi : fork_list)
- if (fi.ptid == ptid)
- return &fi;
+ for (inferior *inf : all_inferiors (linux_target))
+ {
+ for (fork_info &fi : fork_list (inf))
+ if (fi.ptid == ptid)
+ return { &fi, inf };
+ }
- return NULL;
+ return { nullptr, nullptr };
}
-/* Find a fork_info by matching ID. */
-static struct fork_info *
-find_fork_id (int num)
+/* Find a fork_info by matching NUM in inferior INF. */
+
+static fork_info *
+find_fork_id (inferior *inf, int num)
{
- for (fork_info &fi : fork_list)
+ for (fork_info &fi : fork_list (inf))
if (fi.num == num)
return &fi;
- return NULL;
+ return nullptr;
}
-/* Find a fork_info by matching pid. */
-extern struct fork_info *
+/* Find a fork_info and inferior by matching pid. */
+
+extern std::pair<fork_info *, inferior *>
find_fork_pid (pid_t pid)
{
- for (fork_info &fi : fork_list)
- if (pid == fi.ptid.pid ())
- return &fi;
+ for (inferior *inf : all_inferiors (linux_target))
+ {
+ for (fork_info &fi : fork_list (inf))
+ if (pid == fi.ptid.pid ())
+ return { &fi, inf };
+ }
- return NULL;
+ return { nullptr, nullptr };
}
-static ptid_t
-fork_id_to_ptid (int num)
+/* Parse a command argument representing a checkpoint id. This
+ can take one of two forms:
+
+ Num
+
+ -or-
+
+ Inf.Num
+
+ where Num is a non-negative decimal integer and Inf, if present, is
+ a positive decimal integer.
+
+ Return a pair with a pointer to the fork_info struct and pointer
+ to the inferior. This function will throw an error if there's
+ a problem with the parsing or if either the inferior or checkpoint
+ id does not exist. */
+
+static std::pair<fork_info *, inferior *>
+parse_checkpoint_id (const char *ckptstr)
{
- struct fork_info *fork = find_fork_id (num);
- if (fork)
- return fork->ptid;
+ const char *number = ckptstr;
+ const char *p1;
+ struct inferior *inf;
+
+ const char *dot = strchr (number, '.');
+
+ if (dot != nullptr)
+ {
+ /* Parse number to the left of the dot. */
+ int inf_num;
+
+ p1 = number;
+ inf_num = get_number_trailer (&p1, '.');
+ if (inf_num <= 0)
+ error (_("Inferior number must be a positive integer"));
+
+ inf = find_inferior_id (inf_num);
+ if (inf == NULL)
+ error (_("No inferior number '%d'"), inf_num);
+
+ p1 = dot + 1;
+ }
else
- return ptid_t (-1);
+ {
+ inf = current_inferior ();
+ p1 = number;
+ }
+
+ int fork_num = get_number_trailer (&p1, 0);
+ if (fork_num < 0)
+ error (_("Checkpoint number must be a non-negative integer"));
+
+ if (!forks_exist_p (inf))
+ error (_("Inferior %d has no checkpoints"), inf->num);
+
+ fork_info *fork_ptr = find_fork_id (inf, fork_num);
+ if (fork_ptr == nullptr)
+ error (_("Invalid checkpoint number %d for inferior %d"),
+ fork_num, inf->num);
+
+ return { fork_ptr, inf };
}
/* Fork list <-> gdb interface. */
@@ -294,10 +430,21 @@ fork_save_infrun_state (struct fork_info *fp)
}
}
+/* Given a ptid, return a "process ptid" in which only the pid member
+ is present. This is used in calls to target_pid_to_str() to ensure
+ that only process ptids are printed by this file. */
+
+static inline ptid_t
+proc_ptid (ptid_t ptid)
+{
+ ptid_t process_ptid (ptid.pid ());
+ return process_ptid;
+}
+
/* Kill 'em all, let God sort 'em out... */
void
-linux_fork_killall (void)
+linux_fork_killall (inferior *inf)
{
/* Walk list and kill every pid. No need to treat the
current inferior_ptid as special (we do not return a
@@ -305,6 +452,7 @@ linux_fork_killall (void)
or a parent, so may get a SIGCHLD from a previously
killed child. Wait them all out. */
+ auto &fork_list = ::fork_list (inf);
for (fork_info &fi : fork_list)
{
pid_t pid = fi.ptid.pid ();
@@ -314,7 +462,7 @@ linux_fork_killall (void)
/* Use SIGKILL instead of PTRACE_KILL because the former works even
if the thread is running, while the later doesn't. */
kill (pid, SIGKILL);
- ret = waitpid (pid, &status, 0);
+ ret = gdb::waitpid (pid, &status, 0);
/* We might get a SIGCHLD instead of an exit status. This is
aggravated by the first kill above - a child has just
died. MVS comment cut-and-pasted from linux-nat. */
@@ -323,6 +471,7 @@ linux_fork_killall (void)
/* Clear list, prepare to start fresh. */
fork_list.clear ();
+ reset_highest_fork_num (inf);
}
/* The current inferior_ptid has exited, but there are other viable
@@ -330,35 +479,36 @@ linux_fork_killall (void)
first available. */
void
-linux_fork_mourn_inferior (void)
+linux_fork_mourn_inferior ()
{
struct fork_info *last;
int status;
+ inferior *inf = current_inferior ();
/* Wait just one more time to collect the inferior's exit status.
Do not check whether this succeeds though, since we may be
dealing with a process that we attached to. Such a process will
only report its exit status to its original parent. */
- waitpid (inferior_ptid.pid (), &status, 0);
+ gdb::waitpid (inferior_ptid.pid (), &status, 0);
/* OK, presumably inferior_ptid is the one who has exited.
- We need to delete that one from the fork_list, and switch
+ We need to delete that one from the fork list, and switch
to the next available fork. */
- delete_fork (inferior_ptid);
+ delete_fork (inferior_ptid, inf);
/* There should still be a fork - if there's only one left,
delete_fork won't remove it, because we haven't updated
inferior_ptid yet. */
- gdb_assert (!fork_list.empty ());
+ gdb_assert (!fork_list (inf).empty ());
- last = find_last_fork ();
+ last = find_last_fork (inf);
fork_load_infrun_state (last);
gdb_printf (_("[Switching to %s]\n"),
- target_pid_to_str (inferior_ptid).c_str ());
+ target_pid_to_str (proc_ptid (inferior_ptid)).c_str ());
/* If there's only one fork, switch back to non-fork mode. */
- if (one_fork_p ())
- delete_fork (inferior_ptid);
+ if (one_fork_p (inf))
+ delete_fork (inferior_ptid, inf);
}
/* The current inferior_ptid is being detached, but there are other
@@ -366,13 +516,13 @@ linux_fork_mourn_inferior (void)
the first available. */
void
-linux_fork_detach (int from_tty, lwp_info *lp)
+linux_fork_detach (int from_tty, lwp_info *lp, inferior *inf)
{
gdb_assert (lp != nullptr);
gdb_assert (lp->ptid == inferior_ptid);
/* OK, inferior_ptid is the one we are detaching from. We need to
- delete it from the fork_list, and switch to the next available
+ delete it from the fork list, and switch to the next available
fork. But before doing the detach, do make sure that the lwp
hasn't exited or been terminated first. */
@@ -382,25 +532,26 @@ linux_fork_detach (int from_tty, lwp_info *lp)
{
if (ptrace (PTRACE_DETACH, inferior_ptid.pid (), 0, 0))
error (_("Unable to detach %s"),
- target_pid_to_str (inferior_ptid).c_str ());
+ target_pid_to_str (proc_ptid (inferior_ptid)).c_str ());
}
- delete_fork (inferior_ptid);
+ delete_fork (inferior_ptid, inf);
/* There should still be a fork - if there's only one left,
delete_fork won't remove it, because we haven't updated
inferior_ptid yet. */
+ auto &fork_list = ::fork_list (inf);
gdb_assert (!fork_list.empty ());
fork_load_infrun_state (&fork_list.front ());
if (from_tty)
gdb_printf (_("[Switching to %s]\n"),
- target_pid_to_str (inferior_ptid).c_str ());
+ target_pid_to_str (proc_ptid (inferior_ptid)).c_str ());
/* If there's only one fork, switch back to non-fork mode. */
- if (one_fork_p ())
- delete_fork (inferior_ptid);
+ if (one_fork_p (inf))
+ delete_fork (inferior_ptid, inf);
}
/* Temporarily switch to the infrun state stored on the fork_info
@@ -413,19 +564,26 @@ public:
/* Switch to the infrun state held on the fork_info identified by
PPTID. If PPTID is the current inferior then no switch is done. */
explicit scoped_switch_fork_info (ptid_t pptid)
- : m_oldfp (nullptr)
+ : m_oldfp (nullptr), m_oldinf (nullptr)
{
if (pptid != inferior_ptid)
{
- struct fork_info *newfp = nullptr;
-
/* Switch to pptid. */
- m_oldfp = find_fork_ptid (inferior_ptid);
+ auto [oldfp, oldinf] = find_fork_ptid (inferior_ptid);
+ m_oldfp = oldfp;
gdb_assert (m_oldfp != nullptr);
- newfp = find_fork_ptid (pptid);
+ auto [newfp, newinf] = find_fork_ptid (pptid);
gdb_assert (newfp != nullptr);
fork_save_infrun_state (m_oldfp);
remove_breakpoints ();
+
+ if (oldinf != newinf)
+ {
+ thread_info *tp = any_thread_of_inferior (newinf);
+ switch_to_thread (tp);
+ m_oldinf = oldinf;
+ }
+
fork_load_infrun_state (newfp);
insert_breakpoints ();
}
@@ -435,12 +593,17 @@ public:
didn't need to switch states, then nothing is done here either. */
~scoped_switch_fork_info ()
{
- if (m_oldfp != nullptr)
+ if (m_oldinf != nullptr || m_oldfp != nullptr)
{
/* Switch back to inferior_ptid. */
try
{
remove_breakpoints ();
+ if (m_oldinf != nullptr)
+ {
+ thread_info *tp = any_thread_of_inferior (m_oldinf);
+ switch_to_thread (tp);
+ }
fork_load_infrun_state (m_oldfp);
insert_breakpoints ();
}
@@ -459,7 +622,7 @@ public:
catch (const gdb_exception &ex)
{
warning (_("Couldn't restore checkpoint state in %s: %s"),
- target_pid_to_str (m_oldfp->ptid).c_str (),
+ target_pid_to_str (proc_ptid (m_oldfp->ptid)).c_str (),
ex.what ());
}
}
@@ -472,8 +635,16 @@ private:
we were already in the desired state, and nothing needs to be
restored. */
struct fork_info *m_oldfp;
+
+ /* When switching to a different fork, this is the inferior for the
+ fork that we're switching from, and to which we'll switch back once
+ end-of-scope is reached. It may also be nullptr if no switching
+ is required. */
+ inferior *m_oldinf;
};
+/* Call waitpid() by making an inferior function call. */
+
static int
inferior_call_waitpid (ptid_t pptid, int pid)
{
@@ -516,30 +687,27 @@ static void
delete_checkpoint_command (const char *args, int from_tty)
{
ptid_t ptid, pptid;
- struct fork_info *fi;
if (!args || !*args)
error (_("Requires argument (checkpoint id to delete)"));
- ptid = fork_id_to_ptid (parse_and_eval_long (args));
- if (ptid == minus_one_ptid)
- error (_("No such checkpoint id, %s"), args);
+ auto [fi, inf] = parse_checkpoint_id (args);
+ ptid = fi->ptid;
+ gdb_assert (fi != nullptr);
+ pptid = fi->parent_ptid;
- if (ptid == inferior_ptid)
- error (_("\
-Please switch to another checkpoint before deleting the current one"));
+ if (ptid.pid () == inf->pid)
+ error (_("Cannot delete active checkpoint"));
if (ptrace (PTRACE_KILL, ptid.pid (), 0, 0))
- error (_("Unable to kill pid %s"), target_pid_to_str (ptid).c_str ());
-
- fi = find_fork_ptid (ptid);
- gdb_assert (fi);
- pptid = fi->parent_ptid;
+ error (_("Unable to kill pid %s"),
+ target_pid_to_str (proc_ptid (ptid)).c_str ());
if (from_tty)
- gdb_printf (_("Killed %s\n"), target_pid_to_str (ptid).c_str ());
+ gdb_printf (_("Killed %s\n"),
+ target_pid_to_str (proc_ptid (ptid)).c_str ());
- delete_fork (ptid);
+ delete_fork (ptid, inf);
if (pptid == null_ptid)
{
@@ -548,7 +716,7 @@ Please switch to another checkpoint before deleting the current one"));
this succeeds though, since we may be dealing with a process that we
attached to. Such a process will only report its exit status to its
original parent. */
- waitpid (ptid.pid (), &status, 0);
+ gdb::waitpid (ptid.pid (), &status, 0);
return;
}
@@ -557,12 +725,12 @@ Please switch to another checkpoint before deleting the current one"));
If fi->parent_ptid is a part of lwp and it is stopped, waitpid the
ptid. */
thread_info *parent = linux_target->find_thread (pptid);
- if ((parent == NULL && find_fork_ptid (pptid))
+ if ((parent == NULL && find_fork_ptid (pptid).first != nullptr)
|| (parent != NULL && parent->state == THREAD_STOPPED))
{
if (inferior_call_waitpid (pptid, ptid.pid ()))
warning (_("Unable to wait pid %s"),
- target_pid_to_str (ptid).c_str ());
+ target_pid_to_str (proc_ptid (ptid)).c_str ());
}
}
@@ -574,93 +742,221 @@ detach_checkpoint_command (const char *args, int from_tty)
if (!args || !*args)
error (_("Requires argument (checkpoint id to detach)"));
- ptid = fork_id_to_ptid (parse_and_eval_long (args));
- if (ptid == minus_one_ptid)
- error (_("No such checkpoint id, %s"), args);
+ auto fi = parse_checkpoint_id (args).first;
+ ptid = fi->ptid;
if (ptid == inferior_ptid)
error (_("\
Please switch to another checkpoint before detaching the current one"));
if (ptrace (PTRACE_DETACH, ptid.pid (), 0, 0))
- error (_("Unable to detach %s"), target_pid_to_str (ptid).c_str ());
+ error (_("Unable to detach %s"),
+ target_pid_to_str (proc_ptid (ptid)).c_str ());
if (from_tty)
- gdb_printf (_("Detached %s\n"), target_pid_to_str (ptid).c_str ());
+ gdb_printf (_("Detached %s\n"),
+ target_pid_to_str (proc_ptid (ptid)).c_str ());
- delete_fork (ptid);
+ delete_fork (ptid, current_inferior ());
}
-/* Print information about currently known checkpoints. */
+/* Helper for info_checkpoints_command. */
static void
-info_checkpoints_command (const char *arg, int from_tty)
+print_checkpoints (struct ui_out *uiout, inferior *req_inf, fork_info *req_fi)
{
- struct gdbarch *gdbarch = get_current_arch ();
- int requested = -1;
- bool printed = false;
-
- if (arg && *arg)
- requested = (int) parse_and_eval_long (arg);
-
- for (const fork_info &fi : fork_list)
+ struct inferior *cur_inf = current_inferior ();
+ bool will_print_something = false;
+
+ /* Figure out whether to print the inferior number in the
+ checkpoint list. */
+ bool print_inf = (number_of_inferiors () > 1);
+
+ /* Compute widths of some of the table components. */
+ size_t inf_width = 0;
+ size_t num_width = 0;
+ size_t targid_width = 0;
+ for (inferior *inf : all_inferiors (linux_target))
{
- if (requested > 0 && fi.num != requested)
+ if (req_inf != nullptr && req_inf != inf)
continue;
- printed = true;
-
- bool is_current = fi.ptid == inferior_ptid;
- if (is_current)
- gdb_printf ("* ");
- else
- gdb_printf (" ");
- gdb_printf ("%d %s", fi.num, target_pid_to_str (fi.ptid).c_str ());
- if (fi.num == 0)
- gdb_printf (_(" (main process)"));
+ scoped_restore_current_pspace_and_thread restore_pspace_thread;
+ switch_to_program_space_and_thread (inf->pspace);
- if (is_current && inferior_thread ()->state == THREAD_RUNNING)
+ for (const fork_info &fi : fork_list (inf))
{
- gdb_printf (_(" <running>\n"));
- continue;
- }
-
- gdb_printf (_(" at "));
- ULONGEST pc
- = (is_current
- ? regcache_read_pc (get_thread_regcache (inferior_thread ()))
- : fi.pc);
- gdb_puts (paddress (gdbarch, pc));
-
- symtab_and_line sal = find_pc_line (pc, 0);
- if (sal.symtab)
- gdb_printf (_(", file %s"),
- symtab_to_filename_for_display (sal.symtab));
- if (sal.line)
- gdb_printf (_(", line %d"), sal.line);
- if (!sal.symtab && !sal.line)
- {
- bound_minimal_symbol msym = lookup_minimal_symbol_by_pc (pc);
- if (msym.minsym)
- gdb_printf (", <%s>", msym.minsym->linkage_name ());
+ if (req_fi != nullptr && req_fi != &fi)
+ continue;
+
+ will_print_something = true;
+
+ inf_width
+ = std::max (inf_width,
+ string_printf ("%d", inf->num).size ());
+ num_width
+ = std::max (num_width,
+ string_printf ("%d", fi.num).size ()
+ + (print_inf ? 1 : 0));
+ targid_width
+ = std::max (targid_width,
+ target_pid_to_str (proc_ptid (fi.ptid)).size ());
}
+ }
- gdb_putc ('\n');
+ /* Return early if there are no checkpoints to print. */
+ if (!will_print_something)
+ {
+ gdb_printf (_("No checkpoints.\n"));
+ return;
}
- if (!printed)
+ /* Ensure that column header width doesn't exceed that of the column data
+ for the Id field. */
+ if (!print_inf && num_width < 2)
+ num_width = 2;
+
+ ui_out_emit_table table_emitter (uiout, 5, -1, "checkpoints");
+
+ /* Define the columns / headers... */
+ uiout->table_header (1, ui_left, "current", "");
+ uiout->table_header ((print_inf ? (int) inf_width : 0) + (int) num_width,
+ ui_right, "id", "Id");
+ uiout->table_header (6, ui_left, "active", "Active");
+ uiout->table_header (targid_width, ui_left, "target-id", "Target Id");
+ uiout->table_header (1, ui_left, "frame", "Frame");
+ uiout->table_body ();
+
+ for (inferior *inf : all_inferiors (linux_target))
{
- if (requested > 0)
- gdb_printf (_("No checkpoint number %d.\n"), requested);
- else
- gdb_printf (_("No checkpoints.\n"));
+ /* If asked to print a partciular inferior, skip all of
+ those which don't match. */
+ if (req_inf != nullptr && req_inf != inf)
+ continue;
+
+ scoped_restore_current_pspace_and_thread restore_pspace_thread;
+ switch_to_program_space_and_thread (inf->pspace);
+
+ for (const fork_info &fi : fork_list (inf))
+ {
+ /* If asked to print a particular checkpoint, skip all
+ which don't match. */
+ if (req_fi != nullptr && req_fi != &fi)
+ continue;
+
+ thread_info *t = any_thread_of_inferior (inf);
+ bool is_current = fi.ptid.pid () == inf->pid;
+
+ ui_out_emit_tuple tuple_emitter (uiout, nullptr);
+
+ if (is_current && cur_inf == inf)
+ uiout->field_string ("current", "*");
+ else
+ uiout->field_skip ("current");
+
+ if (print_inf)
+ uiout->field_fmt ("id", "%d.%d", inf->num, fi.num);
+ else
+ uiout->field_fmt ("id", "%d", fi.num);
+
+ /* Print out 'y' or 'n' for whether the checkpoint is current. */
+ uiout->field_string ("active", is_current ? "y" : "n");
+
+ /* Print target id. */
+ uiout->field_string
+ ("target-id", target_pid_to_str (proc_ptid (fi.ptid)).c_str ());
+
+ if (t->state == THREAD_RUNNING && is_current)
+ uiout->text ("(running)");
+ else
+ {
+ /* Print frame info for the checkpoint under
+ consideration.
+
+ Ideally, we'd call print_stack_frame() here in order
+ to have consistency (with regard to how frames are
+ printed) with other parts of GDB as well as to reduce
+ the amount of code required here.
+
+ However, we can't simply print the frame without
+ switching checkpoint contexts. To do that, we could
+ first call scoped_switch_fork_info() - that mostly
+ works - except when the active fork/checkpoint is
+ running, i.e. when t->state == THREAD_RUNNING.
+ Switching context away from a running fork has certain
+ problems associated with it. Certainly, the
+ fork_info struct would need some new fields, but
+ work would also need to be done to do something
+ reasonable should the state of the running fork
+ have changed when switching back to it.
+
+ Note: If scoped_switch_fork_info() is someday
+ changed to allow switching from a running
+ fork/checkpoint, then it might also be possible to
+ allow a restart from a running checkpoint to some
+ other checkpoint. */
+
+ ui_out_emit_tuple frame_tuple_emitter (uiout, "frame");
+ uiout->text ("at ");
+
+ ULONGEST pc
+ = (is_current
+ ? regcache_read_pc (get_thread_regcache (t))
+ : fi.pc);
+ uiout->field_core_addr ("addr", get_current_arch (), pc);
+
+ symtab_and_line sal = find_pc_line (pc, 0);
+ if (sal.symtab)
+ {
+ uiout->text (", file ");
+ uiout->field_string ("file",
+ symtab_to_filename_for_display (sal.symtab),
+ file_name_style.style ());
+ }
+ if (sal.line)
+ {
+ uiout->text (", line ");
+ uiout->field_signed ("line", sal.line,
+ line_number_style.style ());
+ }
+ if (!sal.symtab && !sal.line)
+ {
+ bound_minimal_symbol msym = lookup_minimal_symbol_by_pc (pc);
+ if (msym.minsym)
+ {
+ uiout->text (", <");
+ uiout->field_string ("linkage-name",
+ msym.minsym->linkage_name (),
+ function_name_style.style ());
+ uiout->text (">");
+ }
+ }
+ }
+
+ uiout->text ("\n");
+ }
}
}
+/* Print information about currently known checkpoints. */
+
+static void
+info_checkpoints_command (const char *arg, int from_tty)
+{
+ inferior *req_inf = nullptr;
+ fork_info *req_fi = nullptr;
+
+ if (arg && *arg)
+ std::tie (req_fi, req_inf) = parse_checkpoint_id (arg);
+
+ print_checkpoints (current_uiout, req_inf, req_fi);
+
+}
+
/* The PID of the process we're checkpointing. */
static int checkpointing_pid = 0;
-int
+bool
linux_fork_checkpointing_p (int pid)
{
return (checkpointing_pid == pid);
@@ -690,17 +986,16 @@ checkpoint_command (const char *args, int from_tty)
struct target_waitstatus last_target_waitstatus;
ptid_t last_target_ptid;
struct value *fork_fn = NULL, *ret;
- struct fork_info *fp;
pid_t retpid;
- if (!target_has_execution ())
+ if (!target_has_execution ())
error (_("The program is not being run."));
/* Ensure that the inferior is not multithreaded. */
update_thread_list ();
if (inf_has_multiple_threads ())
error (_("checkpoint: can't checkpoint multiple threads."));
-
+
/* Make the inferior fork, record its (and gdb's) state. */
if (lookup_minimal_symbol (current_program_space, "fork").minsym != nullptr)
@@ -729,14 +1024,21 @@ checkpoint_command (const char *args, int from_tty)
retpid = value_as_long (ret);
get_last_target_status (nullptr, &last_target_ptid, &last_target_waitstatus);
- fp = find_fork_pid (retpid);
+ auto [fp, inf] = find_fork_pid (retpid);
+
+ if (!fp)
+ error (_("Failed to find new fork"));
if (from_tty)
{
int parent_pid;
- gdb_printf (_("checkpoint %d: fork returned pid %ld.\n"),
- fp != NULL ? fp->num : -1, (long) retpid);
+ gdb_printf (_("Checkpoint %s: fork returned pid %ld.\n"),
+ ((number_of_inferiors () > 1)
+ ? string_printf ("%d.%d", inf->num, fp->num).c_str ()
+ : string_printf ("%d", fp->num).c_str ()),
+ (long) retpid);
+
if (info_verbose)
{
parent_pid = last_target_ptid.lwp ();
@@ -747,15 +1049,12 @@ checkpoint_command (const char *args, int from_tty)
}
}
- if (!fp)
- error (_("Failed to find new fork"));
-
- if (one_fork_p ())
+ if (one_fork_p (inf))
{
/* Special case -- if this is the first fork in the list (the
- list was hitherto empty), then add inferior_ptid first, as a
- special zeroeth fork id. */
- fork_list.emplace_front (inferior_ptid.pid ());
+ list was hitherto empty), then add inferior_ptid as a special
+ zeroeth fork id. */
+ fork_list (inf).emplace_front (inferior_ptid.pid (), 0);
}
fork_save_infrun_state (fp);
@@ -763,45 +1062,60 @@ checkpoint_command (const char *args, int from_tty)
}
static void
-linux_fork_context (struct fork_info *newfp, int from_tty)
+linux_fork_context (struct fork_info *newfp, int from_tty, inferior *newinf)
{
- /* Now we attempt to switch processes. */
- struct fork_info *oldfp;
+ bool inferior_changed = false;
+ /* Now we attempt to switch processes. */
gdb_assert (newfp != NULL);
- oldfp = find_fork_ptid (inferior_ptid);
- gdb_assert (oldfp != NULL);
+ if (newinf != current_inferior ())
+ {
+ thread_info *tp = any_thread_of_inferior (newinf);
+ switch_to_thread (tp);
+ inferior_changed = true;
+ }
- fork_save_infrun_state (oldfp);
- remove_breakpoints ();
- fork_load_infrun_state (newfp);
- insert_breakpoints ();
+ auto oldfp = find_fork_ptid (inferior_ptid).first;
+ gdb_assert (oldfp != NULL);
- gdb_printf (_("Switching to %s\n"),
- target_pid_to_str (inferior_ptid).c_str ());
+ if (oldfp != newfp)
+ {
+ fork_save_infrun_state (oldfp);
+ remove_breakpoints ();
+ fork_load_infrun_state (newfp);
+ insert_breakpoints ();
+ if (!inferior_changed)
+ gdb_printf (_("Switching to %s\n"),
+ target_pid_to_str (proc_ptid (inferior_ptid)).c_str ());
+ }
- print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC, 1);
+ notify_user_selected_context_changed
+ (inferior_changed ? (USER_SELECTED_INFERIOR | USER_SELECTED_FRAME)
+ : USER_SELECTED_FRAME);
}
/* Switch inferior process (checkpoint) context, by checkpoint id. */
+
static void
restart_command (const char *args, int from_tty)
{
- struct fork_info *fp;
-
if (!args || !*args)
error (_("Requires argument (checkpoint id to restart)"));
- if ((fp = find_fork_id (parse_and_eval_long (args))) == NULL)
- error (_("Not found: checkpoint id %s"), args);
+ auto [fp, inf] = parse_checkpoint_id (args);
- linux_fork_context (fp, from_tty);
+ /* Don't allow switching from a thread/fork that's running. */
+ inferior *curinf = current_inferior ();
+ if (curinf->pid != 0
+ && any_thread_of_inferior (curinf)->state == THREAD_RUNNING)
+ error (_("Cannot execute this command while "
+ "the selected thread is running."));
+
+ linux_fork_context (fp, from_tty, inf);
}
-void _initialize_linux_fork ();
-void
-_initialize_linux_fork ()
+INIT_GDB_FILE (linux_fork)
{
/* Checkpoint command: create a fork of the inferior process
and set it aside for later debugging. */