diff options
author | Michael Snyder <msnyder@vmware.com> | 2005-11-22 00:42:28 +0000 |
---|---|---|
committer | Michael Snyder <msnyder@vmware.com> | 2005-11-22 00:42:28 +0000 |
commit | 8ddfc17185f2dddb336e4f2bb3698abfa0f5b241 (patch) | |
tree | fd1d188b905f7a3fa15709c06081bc7a583dd497 | |
parent | 45be59f9e527a7515a36f57ac158784301916552 (diff) | |
download | gdb-8ddfc17185f2dddb336e4f2bb3698abfa0f5b241.zip gdb-8ddfc17185f2dddb336e4f2bb3698abfa0f5b241.tar.gz gdb-8ddfc17185f2dddb336e4f2bb3698abfa0f5b241.tar.bz2 |
2005-11-21 Michael Snyder <msnyder@redhat.com>
* linux-nat.c: Add crude checkpoint/restart using forks.
(child_follow_fork): Put a conditional around PTRACE_DETACH.
FIXME needs to be done in the parent branch too.
(__initialize_linux_nat): Call checkpoint_init.
(struct fork_info): Analog of thread_info.
(fork_load_infrun_state, fork_save_infrun_state):
Save and restore necessary info for fork context switch.
(add_fork, free_fork, delete_fork, find_fork_id, init_fork_list,
find_fork_ptid, iterate_over_forks, valid_fork_id, pid_to_fork_id,
fork_id_to_ptid, in_fork_list): Analogs to thread_list methods.
(info_forks_command): Implement "info forks" (info checkpoints).
(delete_checkpoint): Implement "delete-checkpoint" command.
(checkpoint_command): Implement "checkpoint" command.
(restart_command): Implement "restart" command.
(checkpoint_init): Initialization for checkpoint sub-module.
* infrun.c (set_last_target_status): Supporting function
for checkpoint/restart based on forks (linux).
-rw-r--r-- | gdb/ChangeLog | 22 | ||||
-rw-r--r-- | gdb/infrun.c | 7 | ||||
-rw-r--r-- | gdb/linux-nat.c | 388 |
3 files changed, 416 insertions, 1 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index bd99d31..f7bc6a9 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,5 +1,27 @@ 2005-11-21 Michael Snyder <msnyder@redhat.com> + * linux-nat.c: Add crude checkpoint/restart using forks. + + (child_follow_fork): Put a conditional around PTRACE_DETACH. + FIXME needs to be done in the parent branch too. + (__initialize_linux_nat): Call checkpoint_init. + (struct fork_info): Analog of thread_info. + (fork_load_infrun_state, fork_save_infrun_state): + Save and restore necessary info for fork context switch. + (add_fork, free_fork, delete_fork, find_fork_id, init_fork_list, + find_fork_ptid, iterate_over_forks, valid_fork_id, pid_to_fork_id, + fork_id_to_ptid, in_fork_list): Analogs to thread_list methods. + (info_forks_command): Implement "info forks" (info checkpoints). + (delete_checkpoint): Implement "delete-checkpoint" command. + (checkpoint_command): Implement "checkpoint" command. + (restart_command): Implement "restart" command. + (checkpoint_init): Initialization for checkpoint sub-module. + + * infrun.c (set_last_target_status): Supporting function + for checkpoint/restart based on forks (linux). + +2005-11-21 Michael Snyder <msnyder@redhat.com> + * gcore.c: Add restore-corefile-command. (call_target_sbrk): Abstracted from derive_heap_segment. diff --git a/gdb/infrun.c b/gdb/infrun.c index fab0a96..cd8bf1d 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -1102,6 +1102,13 @@ get_last_target_status (ptid_t *ptidp, struct target_waitstatus *status) *status = target_last_waitstatus; } +void +set_last_target_status (ptid_t ptid, struct target_waitstatus status) +{ + target_last_wait_ptid = ptid; + target_last_waitstatus = status; +} + /* Switch thread contexts, maintaining "infrun state". */ static void diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c index 84ee5d4..1f5fb36 100644 --- a/gdb/linux-nat.c +++ b/gdb/linux-nat.c @@ -46,6 +46,8 @@ #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 @@ -337,6 +339,8 @@ linux_child_post_startup_inferior (ptid_t ptid) linux_enable_event_reporting (ptid); } +int forky_forky; + int child_follow_fork (struct target_ops *ops, int follow_child) { @@ -371,7 +375,9 @@ child_follow_fork (struct target_ops *ops, int follow_child) child_pid); } - ptrace (PTRACE_DETACH, child_pid, 0, 0); + /* Don't detach if doing forky command. */ + if (!forky_forky) + ptrace (PTRACE_DETACH, child_pid, 0, 0); if (has_vforked) { @@ -3208,6 +3214,7 @@ linux_target (void) void _initialize_linux_nat (void) { + static void checkpoint_init (void); struct sigaction action; extern void thread_db_init (struct target_ops *); @@ -3245,6 +3252,7 @@ Enables printf debugging output."), NULL, show_debug_linux_nat, &setdebuglist, &showdebuglist); + checkpoint_init (); } @@ -3312,3 +3320,381 @@ lin_thread_get_thread_signals (sigset_t *set) /* ... except during a sigsuspend. */ sigdelset (&suspend_mask, cancel); } + +/* Hack and slash, steal code from all over the place, + and just try stuff out. */ + +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 inferior_status *status; + struct regcache *savedregs; + + ptid_t last_target_ptid; + struct target_waitstatus last_target_waitstatus; +}; + +/* Load infrun state for the fork PTID. */ + +static void +fork_load_infrun_state (struct fork_info *fp) +{ + if (fp->status) + { + restore_inferior_status (fp->status); + fp->status = NULL; + } + if (fp->savedregs) + regcache_cpy (current_regcache, fp->savedregs); + set_last_target_status (fp->ptid, fp->last_target_waitstatus); +} + +/* Save infrun state for the fork PTID. */ + +static void +fork_save_infrun_state (struct fork_info *fp) +{ + fp->status = save_inferior_status (0); + fp->savedregs = regcache_dup (current_regcache); + get_last_target_status (&fp->last_target_ptid, + &fp->last_target_waitstatus); +} + +static struct fork_info *fork_list = NULL; +static int highest_fork_num; + +struct fork_info * +add_fork (ptid_t ptid) +{ + struct fork_info *fp; + + fp = (struct fork_info *) xmalloc (sizeof (*fp)); + memset (fp, 0, sizeof (*fp)); + fp->ptid = ptid; + 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->status) + xfree (fp->status); + if (fp->savedregs) + regcache_xfree (fp->savedregs); + xfree (fp); + } +} + +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. */ +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; +} + +/* + * 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; +} + +int +pid_to_fork_id (ptid_t ptid) +{ + struct fork_info *fp; + + for (fp = fork_list; fp; fp = fp->next) + if (ptid_equal (fp->ptid, ptid)) + return fp->num; + + 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)"); + + 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); +} + +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 ("* "); + else + printf_filtered (" "); + printf_filtered ("%d %s", fp->num, target_tid_to_str (fp->ptid)); + regcache_raw_read_unsigned (fp->savedregs, PC_REGNUM, &pc); + 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); + + 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; + 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); + forky_forky = 1; + ret = call_function_by_hand (fork_fn, 0, &ret); + forky_forky = 0; + 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 = add_fork (pid_to_ptid (retpid)); + fork_save_infrun_state (fp); + + /* FIXME: The new fork is still in the call_function_by_hand + state, in the call to fork. It should be sufficient to just + copy its registers from the current state. */ + + 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 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; + + 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 (inferior_ptid); + } + + fork_save_infrun_state (oldfp); + inferior_ptid = newfp->ptid; + fork_load_infrun_state (newfp); + flush_cached_frames (); + registers_changed (); + stop_pc = read_pc (); + select_frame (get_current_frame ()); + printf_filtered ("Switching to %s\n", + target_pid_or_tid_to_str (inferior_ptid)); + print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC); +} + + + + +static void +checkpoint_init (void) +{ + init_fork_list (); + add_com ("checkpoint", class_obscure, checkpoint_command, _("\ +Fork a duplicate process (experimental).")); + add_com ("restart", class_obscure, restart_command, _("\ +Flip from parent to child (experimental).")); + add_com ("delete-checkpoint", class_obscure, delete_checkpoint, _("\ +Delete a fork/checkpoint (experimental).")); + add_info ("checkpoints", info_forks_command, + _("IDs of currently known forks/checkpoints.")); + add_info_alias ("forks", "checkpoints", 0); +} + |