diff options
author | Michael Snyder <msnyder@vmware.com> | 2005-11-28 22:28:33 +0000 |
---|---|---|
committer | Michael Snyder <msnyder@vmware.com> | 2005-11-28 22:28:33 +0000 |
commit | 65e867da06bae863a10781dd58add44fe4f54791 (patch) | |
tree | 963d8e937d808827cf74083e24e4029f9a57e203 | |
parent | 1d2d1dd46fb45402443192a14cd2e485596f242f (diff) | |
download | gdb-65e867da06bae863a10781dd58add44fe4f54791.zip gdb-65e867da06bae863a10781dd58add44fe4f54791.tar.gz gdb-65e867da06bae863a10781dd58add44fe4f54791.tar.bz2 |
2005-11-28 Michael Snyder <msnyder@redhat.com>
* linux-fork.c: New file. Move fork list and fork commands here.
* linux-fork.h: Now acts as interface between linux-nat and linux-fork.
* linux-nat.c: Move all fork-related functions to linux-fork.c.
(child_mourn_inferior): Use new API to linux-fork, instead of
manipulating the fork list directly.
(kill_inferior): Ditto.
-rw-r--r-- | gdb/ChangeLog | 9 | ||||
-rw-r--r-- | gdb/linux-fork.c | 545 | ||||
-rw-r--r-- | gdb/linux-fork.h | 33 | ||||
-rw-r--r-- | gdb/linux-nat.c | 510 |
4 files changed, 599 insertions, 498 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 0c49ef3..66cf234 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,12 @@ +2005-11-28 Michael Snyder <msnyder@redhat.com> + + * linux-fork.c: New file. Move fork list and fork commands here. + * linux-fork.h: Now acts as interface between linux-nat and linux-fork. + * linux-nat.c: Move all fork-related functions to linux-fork.c. + (child_mourn_inferior): Use new API to linux-fork, instead of + manipulating the fork list directly. + (kill_inferior): Ditto. + 2005-11-26 Michael Snyder <msnyder@redhat.com> * linux-nat.c (super_mourn_inferior): New function pointer. diff --git a/gdb/linux-fork.c b/gdb/linux-fork.c new file mode 100644 index 0000000..a262238 --- /dev/null +++ b/gdb/linux-fork.c @@ -0,0 +1,545 @@ +/* GNU/Linux native-dependent code for debugging multiple forks. + + Copyright 2005 Free Software Foundation, Inc. + + This file is part of GDB. + + 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 2 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#include "defs.h" /* Standard includes */ +#include "inferior.h" +#include "regcache.h" /* For regcache copy/restore */ +#include "gdbcmd.h" +#include "infcall.h" /* For call_function_by_hand */ + +#include "linux-fork.h" /* External interface */ + +#include <sys/ptrace.h> +#include <sys/wait.h> + +struct fork_info *fork_list; +static int highest_fork_num; + +int detach_fork = 1; /* Default behavior is to detach + newly forked processes (legacy). */ + +/* Fork list data structure: */ +struct fork_info +{ + struct fork_info *next; + ptid_t ptid; /* "Actual process id"; + In fact, this may be overloaded with + kernel thread id, etc. */ + int num; /* Convenient handle (GDB fork id) */ + struct regcache *savedregs; /* Convenient for info fork, saves + having to actually switch contexts. */ + int clobber_regs; /* True if we should restore saved regs. */ + int been_restarted; /* One time flag. */ +}; + +/* + * Fork list methods: + */ + +/* Add a fork to internal fork list. + Called from linux child_follow_fork. */ + +extern struct fork_info * +add_fork (pid_t pid) +{ + struct fork_info *fp; + + if (fork_list == NULL && + pid != PIDGET (inferior_ptid)) + { + /* Special case -- if this is the first fork in the list + (the list is hitherto empty), and if this new fork is + NOT the current inferior_ptid, then add inferior_ptid + first, as a special zeroeth fork id. */ + highest_fork_num = -1; + add_fork (PIDGET (inferior_ptid)); /* safe recursion */ + } + + fp = XZALLOC (struct fork_info); + fp->ptid = pid_to_ptid (pid); + fp->num = ++highest_fork_num; + fp->next = fork_list; + fork_list = fp; + return fp; +} + +static void +free_fork (struct fork_info *fp) +{ + /* FIXME: take care of any left-over step_resume breakpoints. */ + if (fp) + { + if (fp->savedregs) + regcache_xfree (fp->savedregs); + xfree (fp); + } +} + +static void +delete_fork (ptid_t ptid) +{ + struct fork_info *fp, *fpprev; + + fpprev = NULL; + + for (fp = fork_list; fp; fpprev = fp, fp = fp->next) + if (ptid_equal (fp->ptid, ptid)) + break; + + if (!fp) + return; + + if (fpprev) + fpprev->next = fp->next; + else + fork_list = fp->next; + + free_fork (fp); + + /* 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 (fork_list != NULL && fork_list->next == NULL && + ptid_equal (fork_list->ptid, inferior_ptid)) + { + /* Last fork -- delete from list and handle as solo process. */ + /* (Should be a safe recursion). */ + delete_fork (inferior_ptid); + } +} + +/* Find a fork_info by matching PTID. */ +static struct fork_info * +find_fork_ptid (ptid_t ptid) +{ + struct fork_info *fp; + + for (fp = fork_list; fp; fp = fp->next) + if (ptid_equal (fp->ptid, ptid)) + return fp; + + return NULL; +} + +/* Find a fork_info by matching ID. */ +static struct fork_info * +find_fork_id (int num) +{ + struct fork_info *fp; + + for (fp = fork_list; fp; fp = fp->next) + if (fp->num == num) + return fp; + + return NULL; +} + +/* Find a fork_info by matching pid. */ +extern struct fork_info * +find_fork_pid (pid_t pid) +{ + struct fork_info *fp; + + for (fp = fork_list; fp; fp = fp->next) + if (pid == ptid_get_pid (fp->ptid)) + return fp; + + return NULL; +} + +static ptid_t +fork_id_to_ptid (int num) +{ + struct fork_info *fork = find_fork_id (num); + if (fork) + return fork->ptid; + else + return pid_to_ptid (-1); +} + +/* FIXME: */ +static void +init_fork_list (void) +{ + struct fork_info *fp, *fpnext; + + highest_fork_num = 0; + if (!fork_list) + return; + + for (fp = fork_list; fp; fp = fpnext) + { + fpnext = fp->next; + free_fork (fp); + } + + fork_list = NULL; +} + +/* + * Fork list <-> gdb interface: + */ + +/* Load infrun state for the fork PTID. */ + +static void +fork_load_infrun_state (struct fork_info *fp) +{ + extern void nullify_last_target_wait_ptid (); + + if (fp->savedregs && fp->clobber_regs) + regcache_cpy (current_regcache, fp->savedregs); + + nullify_last_target_wait_ptid (); +} + +/* Save infrun state for the fork PTID. + * Exported for use by linux child_follow_fork. + */ + +extern void +fork_save_infrun_state (struct fork_info *fp, int clobber_regs) +{ + if (fp->savedregs) + regcache_xfree (fp->savedregs); + + fp->savedregs = regcache_dup (current_regcache); + fp->clobber_regs = clobber_regs; +} + +/* linux_fork_killall. Let God sort 'em out... */ + +extern void +linux_fork_killall (void) +{ + /* Walk list and kill every pid. No need to treat the + current inferior_ptid as special (we do not return a + status for it) -- however any process may be a child + or a parent, so may get a SIGCHLD from a previously + killed child. Wait them all out. */ + pid_t pid, ret; + int status; + + do { + pid = PIDGET (fork_list->ptid); + do { + ptrace (PT_KILL, pid, 0, 0); + ret = waitpid (pid, &status, 0); + } while (ret == pid && WIFSTOPPED (status)); + delete_fork (fork_list->ptid); + } while (fork_list != NULL); +} + +/* linux_fork_mourn_inferior. The current inferior_ptid has exited, + but there are other viable forks to debug. Delete the exiting one + and context-switch to the first available. +*/ +extern void +linux_fork_mourn_inferior (void) +{ + /* 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 origional parent. */ + int status; + + waitpid (ptid_get_pid (inferior_ptid), &status, 0); + + /* OK, presumably inferior_ptid is the one who has exited. + We need to delete that one from the fork_list, and switch + to the next available fork. FIXME safety? */ + delete_fork (inferior_ptid); + inferior_ptid = fork_list[0].ptid; + printf_filtered ("[Switching to %s]\n", + target_pid_to_str (inferior_ptid)); +} + +/* + * Fork list <-> user interface: + */ + +static void +delete_fork_command (char *args, int from_tty) +{ + ptid_t ptid; + + if (!args || !*args) + error ("Requires argument (checkpoint id to delete, see info checkpoint)"); + + /* FIXME: check for not-found! */ + /* FIXME: we can do better than strtol, too... */ + ptid = fork_id_to_ptid (strtol (args, NULL, 0)); + if (ptrace (PTRACE_KILL, ptid, 0, 0)) + error ("Unable to kill pid %s", target_tid_to_str (ptid)); + + delete_fork (ptid); +} + +static void +detach_fork_command (char *args, int from_tty) +{ + ptid_t ptid; + + if (!args || !*args) + error ("Requires argument (fork id to delete, see info fork)"); + + /* FIXME: check for not-found! */ + /* FIXME: we can do better than strtol, too... */ + ptid = fork_id_to_ptid (strtol (args, NULL, 0)); + if (ptid_equal (ptid, inferior_ptid)) + error ("Please switch to another fork before detaching the current fork"); + + if (ptrace (PTRACE_DETACH, ptid, 0, 0)) + error ("Unable to detach %s", target_pid_to_str (ptid)); + + if (from_tty) + printf_filtered ("Detached %s\n", target_pid_to_str (ptid)); + + delete_fork (ptid); +} + +/* Print information about currently known forks. + */ + +static void +info_forks_command (char *arg, int from_tty) +{ + struct frame_info *cur_frame; + struct symtab_and_line sal; + struct symtab *cur_symtab; + struct fork_info *fp; + int cur_line; + ULONGEST pc; + + for (fp = fork_list; fp; fp = fp->next) + { + if (ptid_equal (fp->ptid, inferior_ptid)) + { + printf_filtered ("* "); + pc = read_pc (); + } + else + { + printf_filtered (" "); + regcache_raw_read_unsigned (fp->savedregs, PC_REGNUM, &pc); + } + printf_filtered ("%d %s", fp->num, target_tid_to_str (fp->ptid)); + if (fp->num == 0) + printf_filtered (" (main process)"); + printf_filtered (" at "); + deprecated_print_address_numeric (pc, 1, gdb_stdout); + + sal = find_pc_line (pc, 0); + if (sal.symtab) + printf_filtered (", file %s", sal.symtab->filename); + if (sal.line) + printf_filtered (", line %d", sal.line); + if (!sal.symtab && !sal.line) + { + struct minimal_symbol *msym; + + msym = lookup_minimal_symbol_by_pc (pc); + if (msym) + printf_filtered (", <%s>", SYMBOL_LINKAGE_NAME (msym)); + } + + putchar_filtered ('\n'); + } +} + +static void +checkpoint_command (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; + int save_detach_fork; + long i; + + /* Make the inferior fork, record its (and gdb's) state. */ + + if (lookup_minimal_symbol ("fork", NULL, NULL) != NULL) + fork_fn = find_function_in_inferior ("fork"); + if (!fork_fn) + if (lookup_minimal_symbol ("_fork", NULL, NULL) != NULL) + fork_fn = find_function_in_inferior ("fork"); + if (!fork_fn) + error ("checkpoint: can't find fork function in inferior."); + + ret = value_from_longest (builtin_type_int, 0); + save_detach_fork = detach_fork; + detach_fork = 0; + ret = call_function_by_hand (fork_fn, 0, &ret); + detach_fork = save_detach_fork; + if (!ret) /* Probably can't happen. */ + error ("checkpoint: call_function_by_hand returned null."); + + retpid = value_as_long (ret); + get_last_target_status (&last_target_ptid, &last_target_waitstatus); + if (from_tty) + { + int parent_pid; + + printf_filtered ("checkpoint: fork returned %ld.\n", (long) retpid); + parent_pid = ptid_get_lwp (last_target_ptid); + if (parent_pid == 0) + parent_pid = ptid_get_pid (last_target_ptid); + printf_filtered (" gdb says parent = %ld.\n", (long) parent_pid); + } + + fp = find_fork_pid (retpid); + if (!fp) + error ("Failed to find new fork"); + fork_save_infrun_state (fp, 1); + + if (info_verbose && from_tty) + { + printf_filtered ("retpid registers:\n"); + errno = 0; + for (i = 0; errno == 0; i += 4) + printf_filtered ("0x%08lx\n", + ptrace (PTRACE_PEEKUSER, retpid, i, 0)); + errno = 0; + } +} + +#include "string.h" + +static int restart_auto_finish; + +static void +restart_command (char *args, int from_tty) +{ + /* Now we attempt to switch processes. */ + struct fork_info *oldfp = find_fork_ptid (inferior_ptid); + struct fork_info *newfp; + ptid_t ptid; + int id, i; + + if (!args || !*args) + error ("Requires argument (checkpoint or fork id, see info checkpoint)"); + + id = strtol (args, NULL, 0); + newfp = find_fork_id (id); + if (!newfp) + error ("No such checkpoint id: %d\n", id); + + if (!oldfp) + { + oldfp = add_fork (ptid_get_pid (inferior_ptid)); + } + + fork_save_infrun_state (oldfp, 1); + oldfp->been_restarted = 1; + inferior_ptid = newfp->ptid; + fork_load_infrun_state (newfp); + registers_changed (); + reinit_frame_cache (); + stop_pc = read_pc (); + select_frame (get_current_frame ()); + + if (!newfp->been_restarted) + for (i = 0; i < restart_auto_finish; i++) + { + execute_command ("finish", from_tty); + } + + newfp->been_restarted = 1; + printf_filtered ("Switching to %s\n", + target_pid_to_str (inferior_ptid)); + + print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC); +} + + +/* Extern because called from core gdb. */ +extern void +_initialize_linux_fork (void) +{ + init_fork_list (); + + /* Set/show detach-on-fork: user-settable mode. */ + + add_setshow_boolean_cmd ("detach-on-fork", class_obscure, &detach_fork, _("\ +Set whether gdb will detach the child of a fork."), _("\ +Show whether gdb will detach the child of a fork."), _("\ +Tells gdb whether to detach the child of a fork."), + NULL, NULL, &setlist, &showlist); + + /* Set/show restart-auto-finish: user-settable count. Causes the + first "restart" of a fork to do some number of "finish" commands + before returning to user. + + Useful because otherwise the virgin fork process will be stopped + somewhere in the un-interesting fork system call. */ + + add_setshow_integer_cmd ("restart-auto-finish", class_obscure, + &restart_auto_finish, _("\ +Set number of finish commands gdb should do on restart of a fork."), _("\ +Show number of finish commands gdb should do on restart of a fork."), _("\ +Tells gdb how many finish commands to do on restart of a fork."), + NULL, NULL, &setlist, &showlist); + + /* Checkpoint command: create a fork of the inferior process + and set it aside for later debugging. */ + + add_com ("checkpoint", class_obscure, checkpoint_command, _("\ +Fork a duplicate process (experimental).")); + + /* Restart command: restore the context of a specified fork + process. May be used for "program forks" as well as for + "debugger forks" (checkpoints). */ + + add_com ("restart", class_obscure, restart_command, _("\ +Switch between parent and child fork (experimental).")); + + /* Delete-checkpoint command: kill the process and remove it from + fork list. */ + + add_com ("delete-checkpoint", class_obscure, delete_fork_command, _("\ +Delete a fork/checkpoint (experimental).")); + + /* Detach-checkpoint command: release the process to run independantly, + and remove it from the fork list. */ + + add_com ("detach-checkpoint", class_obscure, detach_fork_command, _("\ +Detach from a fork/checkpoint (experimental).")); + + /* Info checkpoints command: list all forks/checkpoints + currently under gdb's control. */ + + add_info ("checkpoints", info_forks_command, + _("IDs of currently known forks/checkpoints.")); + + /* Command aliases (let "fork" and "checkpoint" be used + interchangeably). */ + + add_com_alias ("delete-fork", "delete-checkpoint", class_obscure, 1); + add_com_alias ("detach-fork", "detach-checkpoint", class_obscure, 1); + add_info_alias ("forks", "checkpoints", 0); + + /* "fork <n>" (by analogy to "thread <n>"). */ + + add_com_alias ("fork", "restart", class_obscure, 1); +} diff --git a/gdb/linux-fork.h b/gdb/linux-fork.h new file mode 100644 index 0000000..c4c93ae --- /dev/null +++ b/gdb/linux-fork.h @@ -0,0 +1,33 @@ +/* GNU/Linux native-dependent code for debugging multiple forks. + + Copyright 2005 Free Software Foundation, Inc. + + This file is part of GDB. + + 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 2 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +struct fork_info; +extern struct fork_info *add_fork (pid_t); +extern struct fork_info *find_fork_pid (pid_t); +extern void fork_save_infrun_state (struct fork_info *, int); +extern void linux_fork_killall (void); +extern void linux_fork_mourn_inferior (void); + +struct fork_info *fork_list; +#define FORKS_EXIST() (fork_list != NULL) + +extern int detach_fork; + diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c index ebba1b1..74a48f1 100644 --- a/gdb/linux-nat.c +++ b/gdb/linux-nat.c @@ -47,8 +47,6 @@ #include "gdb_stat.h" /* for struct stat */ #include <fcntl.h> /* for O_RDONLY */ -#include "infcall.h" /* for call_function_by_hand */ - #ifndef O_LARGEFILE #define O_LARGEFILE 0 #endif @@ -346,8 +344,6 @@ linux_child_post_startup_inferior (ptid_t ptid) linux_enable_event_reporting (ptid); } -static int detach_fork = 1; - int child_follow_fork (struct target_ops *ops, int follow_child) { @@ -374,7 +370,7 @@ child_follow_fork (struct target_ops *ops, int follow_child) also, but they'll be reinserted below. */ detach_breakpoints (child_pid); - /* Don't detach if doing forky command. */ + /* Detach new forked process? */ if (detach_fork) { if (1/*debug_linux_nat*/) @@ -621,22 +617,9 @@ kill_inferior (void) return; /* First cut -- let's crudely do everything inline. */ - if (fork_list) + if (FORKS_EXIST ()) { - /* Walk list and kill every pid. No need to treat the - current inferior_ptid as special (we do not return a - status for it) -- however any process may be a child - or a parent, so may get a SIGCHLD from a previously - killed child. Wait them all out. */ - - do { - pid = PIDGET (fork_list->ptid); - do { - ptrace (PT_KILL, pid, 0, 0); - ret = waitpid (pid, &status, 0); - } while (ret == pid && WIFSTOPPED (status)); - delete_fork (fork_list->ptid); - } while (fork_list != NULL); + linux_fork_killall (); } else { @@ -1800,37 +1783,20 @@ child_mourn_inferior (void) { int status; - if (fork_list && - fork_list->next == NULL && - ptid_equal (fork_list->ptid, inferior_ptid)) - { - /* Last fork -- delete from list and handle as solo process. */ - delete_fork (inferior_ptid); - } - - if (fork_list == NULL) + if (! FORKS_EXIST ()) { /* Normal case, no other forks available. */ super_mourn_inferior (); return; } - - /* Multi-fork case. */ - /* 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 origional parent. */ - waitpid (ptid_get_pid (inferior_ptid), &status, 0); - - /* OK, presumably inferior_ptid is the one who has exited. - We need to delete that one from the fork_list, and switch - to the next available fork. FIXME safety? */ - delete_fork (inferior_ptid); - inferior_ptid = fork_list[0].ptid; - printf_filtered ("[Switching to %s]\n", - target_pid_to_str (inferior_ptid)); - - /* Is that enough? Maybe infrun will take care of everything else... */ + else + { + /* Multi-fork case. The current inferior_ptid has exited, + but there are other viable forks to debug. Delete the + exiting one and context-switch to the first available. + */ + linux_fork_mourn_inferior (); + } } /* We need to override child_wait to support attaching to cloned @@ -3315,7 +3281,6 @@ linux_target (void) void _initialize_linux_nat (void) { - static void checkpoint_init (void); struct sigaction action; extern void thread_db_init (struct target_ops *); @@ -3353,7 +3318,6 @@ Enables printf debugging output."), NULL, show_debug_linux_nat, &setdebuglist, &showdebuglist); - checkpoint_init (); } @@ -3422,453 +3386,3 @@ lin_thread_get_thread_signals (sigset_t *set) sigdelset (&suspend_mask, cancel); } -/* Hack and slash, steal code from all over the place, - and just try stuff out. */ - -/* Load infrun state for the fork PTID. */ - -static void -fork_load_infrun_state (struct fork_info *fp) -{ - extern void nullify_last_target_wait_ptid (); - - if (fp->savedregs && fp->clobber_regs) - regcache_cpy (current_regcache, fp->savedregs); - - nullify_last_target_wait_ptid (); -} - -/* Save infrun state for the fork PTID. */ - -extern void -fork_save_infrun_state (struct fork_info *fp, int clobber_regs) -{ - if (fp->savedregs) - regcache_xfree (fp->savedregs); - - fp->savedregs = regcache_dup (current_regcache); - fp->clobber_regs = clobber_regs; -} - -struct fork_info *fork_list = NULL; -static int highest_fork_num; - -extern struct fork_info * -add_fork (pid_t pid) -{ - struct fork_info *fp; - - if (fork_list == NULL && - pid != PIDGET (inferior_ptid)) - { - /* Special case -- if this is the first fork in the list - (the list is hitherto empty), and if this new fork is - NOT the current inferior_ptid, then add inferior_ptid - first, as a special zeroeth fork id. */ - highest_fork_num = -1; - add_fork (PIDGET (inferior_ptid)); /* safe recursion */ - } - - fp = XZALLOC (struct fork_info); - fp->ptid = pid_to_ptid (pid); - fp->num = ++highest_fork_num; - fp->next = fork_list; - fork_list = fp; - return fp; -} - -static void -free_fork (struct fork_info *fp) -{ - /* FIXME: take care of any left-over step_resume breakpoints. */ - if (fp) - { - if (fp->savedregs) - regcache_xfree (fp->savedregs); - xfree (fp); - } -} - -extern void -delete_fork (ptid_t ptid) -{ - struct fork_info *fp, *fpprev; - - fpprev = NULL; - - for (fp = fork_list; fp; fpprev = fp, fp = fp->next) - if (ptid_equal (fp->ptid, ptid)) - break; - - if (!fp) - return; - - if (fpprev) - fpprev->next = fp->next; - else - fork_list = fp->next; - - free_fork (fp); -} - -static struct fork_info * -find_fork_id (int num) -{ - struct fork_info *fp; - - for (fp = fork_list; fp; fp = fp->next) - if (fp->num == num) - return fp; - - return NULL; -} - -void -init_fork_list (void) -{ - struct fork_info *fp, *fpnext; - - highest_fork_num = 0; - if (!fork_list) - return; - - for (fp = fork_list; fp; fp = fpnext) - { - fpnext = fp->next; - free_fork (fp); - } - - fork_list = NULL; -} - -/* Find a fork_info by matching PTID. */ -static struct fork_info * -find_fork_ptid (ptid_t ptid) -{ - struct fork_info *fp; - - for (fp = fork_list; fp; fp = fp->next) - if (ptid_equal (fp->ptid, ptid)) - return fp; - - return NULL; -} - -/* Find a fork_info by matching pid. */ -extern struct fork_info * -find_fork_pid (pid_t pid) -{ - struct fork_info *fp; - - for (fp = fork_list; fp; fp = fp->next) - if (pid == ptid_get_pid (fp->ptid)) - return fp; - - return NULL; -} - - - -/* - * Fork iterator function. - * - * Calls a callback function once for each fork, so long as - * the callback function returns false. If the callback function - * returns true, the iteration will end and the current fork - * will be returned. This can be useful for implementing a - * search for a fork with arbitrary attributes, or for applying - * some operation to every fork. - * - * FIXME: some of the existing functionality, such as - * "Fork apply all", might be rewritten using this functionality. - */ - -struct fork_info * -iterate_over_forks (int (*callback) (struct fork_info *, void *), - void *data) -{ - struct fork_info *fp; - - for (fp = fork_list; fp; fp = fp->next) - if ((*callback) (fp, data)) - return fp; - - return NULL; -} - -int -valid_fork_id (int num) -{ - struct fork_info *fp; - - for (fp = fork_list; fp; fp = fp->next) - if (fp->num == num) - return 1; - - return 0; -} - -ptid_t -fork_id_to_ptid (int num) -{ - struct fork_info *fork = find_fork_id (num); - if (fork) - return fork->ptid; - else - return pid_to_ptid (-1); -} - -static void -delete_checkpoint (char *args, int from_tty) -{ - ptid_t ptid; - - if (!args || !*args) - error ("Requires argument (checkpoint id to delete, see info checkpoint)"); - - /* FIXME: check for not-found! */ - ptid = fork_id_to_ptid (strtol (args, NULL, 0)); - if (ptrace (PTRACE_KILL, ptid, 0, 0)) - error ("Unable to kill pid %s", target_tid_to_str (ptid)); - - delete_fork (ptid); -} - -static void -detach_fork_command (char *args, int from_tty) -{ - ptid_t ptid; - - if (!args || !*args) - error ("Requires argument (fork id to delete, see info fork)"); - - /* FIXME: check for not-found! */ - ptid = fork_id_to_ptid (strtol (args, NULL, 0)); - if (ptid_equal (ptid, inferior_ptid)) - error ("Please switch to another fork before detaching the current fork"); - - if (ptrace (PTRACE_DETACH, ptid, 0, 0)) - error ("Unable to detach %s", target_tid_to_str (ptid)); - - if (from_tty) - printf_filtered ("Detached %s\n", target_pid_or_tid_to_str (ptid)); - - delete_fork (ptid); -} - -int -in_fork_list (ptid_t ptid) -{ - struct fork_info *fp; - - for (fp = fork_list; fp; fp = fp->next) - if (ptid_equal (fp->ptid, ptid)) - return 1; - - return 0; /* Never heard of 'im */ -} - -/* Print information about currently known forks - - * Note: this has the drawback that it _really_ switches - * forks, which frees the frame cache. A no-side - * effects info-forks command would be nicer. - */ - -static void -info_forks_command (char *arg, int from_tty) -{ - struct frame_info *cur_frame; - struct symtab_and_line sal; - struct symtab *cur_symtab; - struct fork_info *fp; - int cur_line; - ULONGEST pc; - - for (fp = fork_list; fp; fp = fp->next) - { - if (ptid_equal (fp->ptid, inferior_ptid)) - { - printf_filtered ("* "); - pc = read_pc (); - } - else - { - printf_filtered (" "); - regcache_raw_read_unsigned (fp->savedregs, PC_REGNUM, &pc); - } - printf_filtered ("%d %s", fp->num, target_tid_to_str (fp->ptid)); - if (fp->num == 0) - printf_filtered (" (main process)"); - printf_filtered (" at "); - deprecated_print_address_numeric (pc, 1, gdb_stdout); - - sal = find_pc_line (pc, 0); - if (sal.symtab) - printf_filtered (", file %s", sal.symtab->filename); - if (sal.line) - printf_filtered (", line %d", sal.line); - if (!sal.symtab && !sal.line) - { - struct minimal_symbol *msym; - - msym = lookup_minimal_symbol_by_pc (pc); - if (msym) - printf_filtered (", <%s>", SYMBOL_LINKAGE_NAME (msym)); - } - - putchar_filtered ('\n'); - } -} - -static void -checkpoint_command (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; - int save_detach_fork; - long i; - - /* Make the inferior fork, record its (and gdb's) state. */ - - if (lookup_minimal_symbol ("fork", NULL, NULL) != NULL) - fork_fn = find_function_in_inferior ("fork"); - if (!fork_fn) - if (lookup_minimal_symbol ("_fork", NULL, NULL) != NULL) - fork_fn = find_function_in_inferior ("fork"); - if (!fork_fn) - error ("checkpoint: can't find fork function in inferior."); - - ret = value_from_longest (builtin_type_int, 0); - save_detach_fork = detach_fork; - detach_fork = 0; - ret = call_function_by_hand (fork_fn, 0, &ret); - detach_fork = save_detach_fork; - if (!ret) /* Probably can't happen. */ - error ("checkpoint: call_function_by_hand returned null."); - - retpid = value_as_long (ret); - get_last_target_status (&last_target_ptid, &last_target_waitstatus); - if (from_tty) - { - int parent_pid; - - printf_filtered ("checkpoint: fork returned %ld.\n", (long) retpid); - parent_pid = ptid_get_lwp (last_target_ptid); - if (parent_pid == 0) - parent_pid = ptid_get_pid (last_target_ptid); - printf_filtered (" gdb says parent = %ld.\n", (long) parent_pid); - } - -#if 0 /* child_follow_fork will have created the new fork. - We still need to save its register state, to update - the savedregs. */ - fp = add_fork (retpid); - fork_save_infrun_state (fp, 1); -#else - fp = find_fork_ptid (pid_to_ptid (retpid)); - if (!fp) - error ("Failed to find new fork"); - fork_save_infrun_state (fp, 1); -#endif - - if (info_verbose && from_tty) - { - printf_filtered ("retpid registers:\n"); - errno = 0; - for (i = 0; errno == 0; i += 4) - printf_filtered ("0x%08lx\n", - ptrace (PTRACE_PEEKUSER, retpid, i, 0)); - errno = 0; - } -} - -#include "string.h" - -static int restart_auto_finish; - -static void -restart_command (char *args, int from_tty) -{ - /* Now we attempt to switch processes. */ - struct fork_info *oldfp = find_fork_ptid (inferior_ptid); - struct fork_info *newfp; - ptid_t ptid; - int id, i; - - if (!args || !*args) - error ("Requires argument (checkpoint or fork id, see info checkpoint)"); - - id = strtol (args, NULL, 0); - newfp = find_fork_id (id); - if (!newfp) - error ("No such checkpoint id: %d\n", id); - - if (!oldfp) - { - oldfp = add_fork (ptid_get_pid (inferior_ptid)); - } - - fork_save_infrun_state (oldfp, 1); - oldfp->been_restarted = 1; - inferior_ptid = newfp->ptid; - fork_load_infrun_state (newfp); - registers_changed (); - /* FIXME lose this. */ -#if 0 - target_fetch_registers (-1); /* FIXME should not be necessary; - fill_gregset should do it automatically. */ -#endif - reinit_frame_cache (); - stop_pc = read_pc (); - select_frame (get_current_frame ()); - - if (!newfp->been_restarted) - for (i = 0; i < restart_auto_finish; i++) - { - execute_command ("finish", from_tty); - } - - newfp->been_restarted = 1; - printf_filtered ("Switching to %s\n", - target_pid_to_str (inferior_ptid)); - - print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC); -} - -static void -checkpoint_init (void) -{ - init_fork_list (); - - add_setshow_boolean_cmd ("detach-on-fork", class_obscure, &detach_fork, _("\ -Set whether gdb will detach the child of a fork."), _("\ -Show whether gdb will detach the child of a fork."), _("\ -Tells gdb whether to detach the child of a fork."), - NULL, NULL, &setlist, &showlist); - - add_setshow_integer_cmd ("restart-auto-finish", class_obscure, - &restart_auto_finish, _("\ -Set number of finish commands gdb should do on restart of a fork."), _("\ -Show number of finish commands gdb should do on restart of a fork."), _("\ -Tells gdb how many finish commands to do on restart of a fork."), - NULL, NULL, &setlist, &showlist); - - - add_com ("checkpoint", class_obscure, checkpoint_command, _("\ -Fork a duplicate process (experimental).")); - add_com ("restart", class_obscure, restart_command, _("\ -Switch between parent and child fork (experimental).")); - add_com_alias ("fork", "restart", class_obscure, 1); - add_com ("delete-checkpoint", class_obscure, delete_checkpoint, _("\ -Delete a fork/checkpoint (experimental).")); - add_com_alias ("delete-fork", "delete-checkpoint", class_obscure, 1); - add_com ("detach-fork", class_obscure, detach_fork_command, _("\ -Detach from a fork/checkpoint (experimental).")); - add_info ("checkpoints", info_forks_command, - _("IDs of currently known forks/checkpoints.")); - add_info_alias ("forks", "checkpoints", 0); -} |