| /* Process record and replay target for GDB, the GNU debugger. |
| |
| Copyright (C) 2008 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 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
| |
| #include "defs.h" |
| #include "target.h" |
| #include "gdbcmd.h" |
| #include "regcache.h" |
| #include "inferior.h" |
| #include "gdbthread.h" |
| #include "record.h" |
| |
| #include <signal.h> |
| |
| #define DEFAULT_RECORD_INSN_MAX_NUM 200000 |
| |
| int record_debug = 0; |
| |
| record_t record_first; |
| record_t *record_list = &record_first; |
| record_t *record_arch_list_head = NULL; |
| record_t *record_arch_list_tail = NULL; |
| struct regcache *record_regcache = NULL; |
| |
| /* 1 ask user. 0 auto delete the last record_t. */ |
| static int record_stop_at_limit = 1; |
| static int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM; |
| static int record_insn_num = 0; |
| |
| struct target_ops record_ops; |
| static int record_resume_step = 0; |
| static enum target_signal record_resume_siggnal; |
| static int record_get_sig = 0; |
| static sigset_t record_maskall; |
| static int in_record_wait = 0; |
| int record_will_store_registers = 0; |
| |
| extern struct bp_location *bp_location_chain; |
| |
| /* The real beneath function pointers. */ |
| void (*record_beneath_to_resume) (ptid_t, int, enum target_signal); |
| ptid_t (*record_beneath_to_wait) (ptid_t, struct target_waitstatus *); |
| void (*record_beneath_to_store_registers) (struct regcache *, int regno); |
| LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops, |
| enum target_object object, |
| const char *annex, |
| gdb_byte * readbuf, |
| const gdb_byte * writebuf, |
| ULONGEST offset, LONGEST len); |
| int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *); |
| int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *); |
| |
| static void |
| record_list_release (record_t * rec) |
| { |
| record_t *tmp; |
| |
| if (!rec) |
| return; |
| |
| while (rec->next) |
| { |
| rec = rec->next; |
| } |
| |
| while (rec->prev) |
| { |
| tmp = rec; |
| rec = rec->prev; |
| if (tmp->type == record_reg) |
| { |
| xfree (tmp->u.reg.val); |
| } |
| else if (tmp->type == record_mem) |
| { |
| xfree (tmp->u.mem.val); |
| } |
| xfree (tmp); |
| } |
| |
| if (rec != &record_first) |
| { |
| xfree (rec); |
| } |
| } |
| |
| static void |
| record_list_release_next (void) |
| { |
| record_t *rec = record_list; |
| record_t *tmp = rec->next; |
| rec->next = NULL; |
| while (tmp) |
| { |
| rec = tmp->next; |
| if (tmp->type == record_reg) |
| { |
| record_insn_num--; |
| } |
| else if (tmp->type == record_reg) |
| { |
| xfree (tmp->u.reg.val); |
| } |
| else if (tmp->type == record_mem) |
| { |
| xfree (tmp->u.mem.val); |
| } |
| xfree (tmp); |
| tmp = rec; |
| } |
| } |
| |
| static void |
| record_list_release_first (void) |
| { |
| record_t *tmp = NULL; |
| enum record_type type; |
| |
| if (!record_first.next) |
| { |
| return; |
| } |
| |
| while (1) |
| { |
| type = record_first.next->type; |
| |
| if (type == record_reg) |
| { |
| xfree (record_first.next->u.reg.val); |
| } |
| else if (type == record_mem) |
| { |
| xfree (record_first.next->u.mem.val); |
| } |
| tmp = record_first.next; |
| record_first.next = tmp->next; |
| xfree (tmp); |
| |
| if (!record_first.next) |
| { |
| gdb_assert (record_insn_num == 1); |
| break; |
| } |
| |
| record_first.next->prev = &record_first; |
| |
| if (type == record_end) |
| { |
| break; |
| } |
| } |
| |
| record_insn_num--; |
| } |
| |
| /* Add a record_t to record_arch_list. */ |
| static void |
| record_arch_list_add (record_t * rec) |
| { |
| if (record_debug > 1) |
| { |
| fprintf_unfiltered (gdb_stdlog, |
| "Process record: record_arch_list_add 0x%s.\n", |
| paddr_nz ((CORE_ADDR)rec)); |
| } |
| |
| if (record_arch_list_tail) |
| { |
| record_arch_list_tail->next = rec; |
| rec->prev = record_arch_list_tail; |
| record_arch_list_tail = rec; |
| } |
| else |
| { |
| record_arch_list_head = rec; |
| record_arch_list_tail = rec; |
| } |
| } |
| |
| /* Record the value of a register ("num") to record_arch_list. */ |
| int |
| record_arch_list_add_reg (int num) |
| { |
| record_t *rec; |
| |
| if (record_debug > 1) |
| { |
| fprintf_unfiltered (gdb_stdlog, |
| "Process record: add register num = %d to record list.\n", |
| num); |
| } |
| |
| rec = (record_t *) xmalloc (sizeof (record_t)); |
| rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); |
| rec->prev = NULL; |
| rec->next = NULL; |
| rec->type = record_reg; |
| rec->u.reg.num = num; |
| |
| regcache_raw_read (record_regcache, num, rec->u.reg.val); |
| |
| record_arch_list_add (rec); |
| |
| return (0); |
| } |
| |
| /* Record the value of a region of memory whose address is "addr" and |
| length is "len" to record_arch_list. */ |
| |
| int |
| record_arch_list_add_mem (CORE_ADDR addr, int len) |
| { |
| record_t *rec; |
| |
| if (record_debug > 1) |
| { |
| fprintf_unfiltered (gdb_stdlog, |
| "Process record: add mem addr = 0x%s len = %d to record list.\n", |
| paddr_nz (addr), len); |
| } |
| |
| if (!addr) |
| { |
| return (0); |
| } |
| |
| rec = (record_t *) xmalloc (sizeof (record_t)); |
| rec->u.mem.val = (gdb_byte *) xmalloc (len); |
| rec->prev = NULL; |
| rec->next = NULL; |
| rec->type = record_mem; |
| rec->u.mem.addr = addr; |
| rec->u.mem.len = len; |
| |
| if (target_read_memory (addr, rec->u.mem.val, len)) |
| { |
| if (record_debug) |
| { |
| fprintf_unfiltered (gdb_stdlog, |
| "Process record: error reading memory at addr = 0x%s len = %d.\n", |
| paddr_nz (addr), len); |
| } |
| xfree (rec->u.mem.val); |
| xfree (rec); |
| return (-1); |
| } |
| |
| record_arch_list_add (rec); |
| |
| return (0); |
| } |
| |
| /* Add a record_end type record_t to record_arch_list. */ |
| int |
| record_arch_list_add_end (int need_dasm) |
| { |
| record_t *rec; |
| |
| if (record_debug > 1) |
| { |
| fprintf_unfiltered (gdb_stdlog, |
| "Process record: add end need_dasm = %d to arch list.\n", |
| need_dasm); |
| } |
| |
| rec = (record_t *) xmalloc (sizeof (record_t)); |
| rec->prev = NULL; |
| rec->next = NULL; |
| rec->type = record_end; |
| |
| rec->u.need_dasm = need_dasm; |
| |
| record_arch_list_add (rec); |
| |
| return (0); |
| } |
| |
| static void |
| record_check_insn_num (int set_terminal) |
| { |
| if (record_insn_max_num) |
| { |
| gdb_assert (record_insn_num <= record_insn_max_num); |
| if (record_insn_num == record_insn_max_num) |
| { |
| /* Ask user what to do. */ |
| if (record_stop_at_limit) |
| { |
| int q; |
| if (set_terminal) |
| target_terminal_ours (); |
| q = yquery (_("Do you want to auto delete previous execution log entries when record/replay buffer becomes full (record-stop-at-limit)?")); |
| if (set_terminal) |
| target_terminal_inferior (); |
| if (q) |
| { |
| record_stop_at_limit = 0; |
| } |
| else |
| { |
| error (_("Process record: inferior program stopped.")); |
| } |
| } |
| } |
| } |
| } |
| |
| /* Before inferior step (when GDB record the running message, inferior |
| only can step), GDB will call this function to record the values to |
| record_list. This function will call gdbarch_process_record to |
| record the running message of inferior and set them to |
| record_arch_list, and add it to record_list. */ |
| |
| static void |
| record_message_cleanups (void *ignore) |
| { |
| record_list_release (record_arch_list_tail); |
| set_executing (inferior_ptid, 0); |
| normal_stop (); |
| } |
| |
| void |
| record_message (struct gdbarch *gdbarch) |
| { |
| int ret; |
| struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); |
| |
| /* Check record_insn_num. */ |
| record_check_insn_num (1); |
| |
| record_arch_list_head = NULL; |
| record_arch_list_tail = NULL; |
| record_regcache = get_current_regcache (); |
| |
| ret = gdbarch_process_record (gdbarch, |
| regcache_read_pc (record_regcache)); |
| if (ret > 0) |
| error (_("Process record: inferior program stopped.")); |
| if (ret < 0) |
| error (_("Process record: failed to record execution log.")); |
| |
| discard_cleanups (old_cleanups); |
| |
| record_list->next = record_arch_list_head; |
| record_arch_list_head->prev = record_list; |
| record_list = record_arch_list_tail; |
| |
| if (record_insn_num == record_insn_max_num && record_insn_max_num) |
| { |
| record_list_release_first (); |
| } |
| else |
| { |
| record_insn_num++; |
| } |
| } |
| |
| /* Things to clean up if we error or QUIT out of function that set |
| in_record_wait (ie. command that caused target_wait to be called). */ |
| static void |
| in_record_wait_cleanups (void *ignore) |
| { |
| in_record_wait = 0; |
| } |
| |
| void |
| in_record_wait_set (void) |
| { |
| struct cleanup *old_cleanups = make_cleanup (in_record_wait_cleanups, 0); |
| in_record_wait = 1; |
| } |
| |
| static void |
| record_open (char *name, int from_tty) |
| { |
| if (record_debug) |
| { |
| fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); |
| } |
| |
| /* check exec */ |
| if (!target_has_execution) |
| { |
| error (_("Process record: the program is not being run.")); |
| } |
| if (non_stop) |
| { |
| error (_("Process record target can't debug inferior in non-stop mode (non-stop).")); |
| } |
| if (target_async_permitted) |
| { |
| error (_("Process record target can't debug inferior in asynchronous mode (target-async).")); |
| } |
| |
| if (!gdbarch_process_record_p (current_gdbarch)) |
| { |
| error (_("Process record: the current architecture doesn't support record function.")); |
| } |
| |
| /* Check if record target is already running. */ |
| if (TARGET_IS_PROCESS_RECORD) |
| { |
| if (!nquery |
| (_("Process record target already running, do you want to delete the old record log?"))) |
| { |
| return; |
| } |
| } |
| |
| push_target (&record_ops); |
| |
| /* Reset */ |
| record_insn_num = 0; |
| record_list = &record_first; |
| record_list->next = NULL; |
| } |
| |
| static void |
| record_close (int quitting) |
| { |
| if (record_debug) |
| { |
| fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); |
| } |
| record_list_release (record_list); |
| } |
| |
| static void |
| record_resume (ptid_t ptid, int step, enum target_signal siggnal) |
| { |
| record_resume_step = step; |
| record_resume_siggnal = siggnal; |
| |
| if (!RECORD_IS_REPLAY) |
| { |
| record_message (current_gdbarch); |
| record_beneath_to_resume (ptid, 1, siggnal); |
| } |
| } |
| |
| static void |
| record_sig_handler (int signo) |
| { |
| if (record_debug) |
| { |
| fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n"); |
| } |
| record_resume_step = 1; |
| record_get_sig = 1; |
| } |
| |
| static void |
| record_wait_cleanups (void *ignore) |
| { |
| if (execution_direction == EXEC_REVERSE) |
| { |
| if (record_list->next) |
| { |
| record_list = record_list->next; |
| } |
| } |
| else |
| { |
| record_list = record_list->prev; |
| } |
| set_executing (inferior_ptid, 0); |
| normal_stop (); |
| } |
| |
| /* record_wait |
| In replay mode, this function examines the recorded log and |
| determines where to stop. */ |
| |
| static ptid_t |
| record_wait (ptid_t ptid, struct target_waitstatus *status) |
| { |
| in_record_wait_set (); |
| if (record_debug) |
| { |
| fprintf_unfiltered (gdb_stdlog, |
| "Process record: record_wait record_resume_step = %d\n", |
| record_resume_step); |
| } |
| |
| if (!RECORD_IS_REPLAY) |
| { |
| if (record_resume_step) |
| { |
| /* This is a single step. */ |
| return record_beneath_to_wait (ptid, status); |
| } |
| else |
| { |
| if (record_resume_step) |
| { |
| /* This is a single step. */ |
| return record_beneath_to_wait (ptid, status); |
| } |
| else |
| { |
| /* This is not a single step. */ |
| ptid_t ret; |
| int is_breakpoint = 1; |
| CORE_ADDR pc = 0; |
| int pc_is_read = 0; |
| struct bp_location *bl; |
| struct breakpoint *b; |
| |
| do |
| { |
| ret = record_beneath_to_wait (ptid, status); |
| |
| if (status->kind == TARGET_WAITKIND_STOPPED |
| && status->value.sig == TARGET_SIGNAL_TRAP) |
| { |
| /* Check if there is a breakpoint. */ |
| pc_is_read = 0; |
| registers_changed (); |
| for (bl = bp_location_chain; bl; bl = bl->global_next) |
| { |
| b = bl->owner; |
| gdb_assert (b); |
| if (b->enable_state != bp_enabled |
| && b->enable_state != bp_permanent) |
| continue; |
| if (!pc_is_read) |
| { |
| pc = |
| regcache_read_pc (get_thread_regcache (ret)); |
| pc_is_read = 1; |
| } |
| switch (b->type) |
| { |
| default: |
| if (bl->address == pc) |
| { |
| goto breakpoint; |
| } |
| break; |
| |
| case bp_watchpoint: |
| /*XXX teawater: I still not very clear how to deal with it. */ |
| goto breakpoint; |
| break; |
| |
| case bp_catchpoint: |
| gdb_assert (b->ops != NULL |
| && b->ops->breakpoint_hit != NULL); |
| if (b->ops->breakpoint_hit (b)) |
| { |
| goto breakpoint; |
| } |
| break; |
| |
| case bp_hardware_watchpoint: |
| case bp_read_watchpoint: |
| case bp_access_watchpoint: |
| if (STOPPED_BY_WATCHPOINT (0)) |
| { |
| goto breakpoint; |
| } |
| break; |
| } |
| } |
| |
| /* There is not a breakpoint. */ |
| record_message (current_gdbarch); |
| record_beneath_to_resume (ptid, 1, |
| record_resume_siggnal); |
| continue; |
| } |
| |
| is_breakpoint = 0; |
| |
| breakpoint: |
| /* Add gdbarch_decr_pc_after_break to pc because gdb will |
| expect the pc to be at address plus decr_pc_after_break |
| when the inferior stops at a breakpoint. */ |
| if (is_breakpoint) |
| { |
| CORE_ADDR decr_pc_after_break = |
| gdbarch_decr_pc_after_break (current_gdbarch); |
| if (decr_pc_after_break) |
| { |
| if (!pc_is_read) |
| { |
| pc = |
| regcache_read_pc (get_thread_regcache (ret)); |
| } |
| regcache_write_pc (get_thread_regcache (ret), |
| pc + decr_pc_after_break); |
| } |
| } |
| |
| break; |
| } |
| while (1); |
| |
| return ret; |
| } |
| } |
| } |
| else |
| { |
| struct sigaction act, old_act; |
| int need_dasm = 0; |
| struct regcache *regcache = get_current_regcache (); |
| int continue_flag = 1; |
| int first_record_end = 1; |
| struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0); |
| CORE_ADDR tmp_pc; |
| |
| status->kind = TARGET_WAITKIND_STOPPED; |
| |
| /* Check breakpoint when forward execute. */ |
| if (execution_direction == EXEC_FORWARD) |
| { |
| tmp_pc = regcache_read_pc (regcache); |
| if (breakpoint_inserted_here_p (tmp_pc)) |
| { |
| if (record_debug) |
| { |
| fprintf_unfiltered (gdb_stdlog, |
| "Process record: break at 0x%s.\n", |
| paddr_nz (tmp_pc)); |
| } |
| if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) |
| && !record_resume_step) |
| { |
| regcache_write_pc (regcache, |
| tmp_pc + |
| gdbarch_decr_pc_after_break |
| (get_regcache_arch (regcache))); |
| } |
| goto replay_out; |
| } |
| } |
| |
| record_get_sig = 0; |
| act.sa_handler = record_sig_handler; |
| act.sa_mask = record_maskall; |
| act.sa_flags = SA_RESTART; |
| if (sigaction (SIGINT, &act, &old_act)) |
| { |
| perror_with_name (_("Process record: sigaction failed")); |
| } |
| /* If GDB is in terminal_inferior mode, it will not get the signal. |
| And in GDB replay mode, GDB doesn't need to be in terminal_inferior |
| mode, because inferior will not executed. |
| Then set it to terminal_ours to make GDB get the signal. */ |
| target_terminal_ours (); |
| |
| /* In EXEC_FORWARD mode, record_list points to the tail of prev |
| instruction. */ |
| if (execution_direction == EXEC_FORWARD && record_list->next) |
| { |
| record_list = record_list->next; |
| } |
| |
| /* Loop over the record_list, looking for the next place to |
| stop. */ |
| do |
| { |
| /* Check for beginning and end of log. */ |
| if (execution_direction == EXEC_REVERSE |
| && record_list == &record_first) |
| { |
| /* Hit beginning of record log in reverse. */ |
| status->kind = TARGET_WAITKIND_NO_HISTORY; |
| break; |
| } |
| if (execution_direction != EXEC_REVERSE && !record_list->next) |
| { |
| /* Hit end of record log going forward. */ |
| status->kind = TARGET_WAITKIND_NO_HISTORY; |
| break; |
| } |
| |
| /* Set ptid, register and memory according to record_list. */ |
| if (record_list->type == record_reg) |
| { |
| /* reg */ |
| gdb_byte reg[MAX_REGISTER_SIZE]; |
| if (record_debug > 1) |
| { |
| fprintf_unfiltered (gdb_stdlog, |
| "Process record: record_reg 0x%s to inferior num = %d.\n", |
| paddr_nz ((CORE_ADDR)record_list), |
| record_list->u.reg.num); |
| } |
| regcache_cooked_read (regcache, record_list->u.reg.num, reg); |
| regcache_cooked_write (regcache, record_list->u.reg.num, |
| record_list->u.reg.val); |
| memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE); |
| } |
| else if (record_list->type == record_mem) |
| { |
| /* mem */ |
| gdb_byte *mem = alloca (record_list->u.mem.len); |
| if (record_debug > 1) |
| { |
| fprintf_unfiltered (gdb_stdlog, |
| "Process record: record_mem 0x%s to inferior addr = 0x%s len = %d.\n", |
| paddr_nz ((CORE_ADDR)record_list), |
| paddr_nz (record_list->u.mem.addr), |
| record_list->u.mem.len); |
| } |
| |
| if (target_read_memory |
| (record_list->u.mem.addr, mem, record_list->u.mem.len)) |
| { |
| error (_("Process record: error reading memory at addr = 0x%s len = %d."), |
| paddr_nz (record_list->u.mem.addr), |
| record_list->u.mem.len); |
| } |
| |
| if (target_write_memory |
| (record_list->u.mem.addr, record_list->u.mem.val, |
| record_list->u.mem.len)) |
| { |
| error (_ |
| ("Process record: error writing memory at addr = 0x%s len = %d."), |
| paddr_nz (record_list->u.mem.addr), |
| record_list->u.mem.len); |
| } |
| memcpy (record_list->u.mem.val, mem, record_list->u.mem.len); |
| } |
| else |
| { |
| if (record_debug > 1) |
| { |
| fprintf_unfiltered (gdb_stdlog, |
| "Process record: record_end 0x%s to inferior need_dasm = %d.\n", |
| paddr_nz ((CORE_ADDR)record_list), |
| record_list->u.need_dasm); |
| } |
| |
| if (execution_direction == EXEC_FORWARD) |
| { |
| need_dasm = record_list->u.need_dasm; |
| } |
| if (need_dasm) |
| { |
| gdbarch_process_record_dasm (current_gdbarch); |
| } |
| |
| if (first_record_end && execution_direction == EXEC_REVERSE) |
| { |
| /* When reverse excute, the first record_end is the part of |
| current instruction. */ |
| first_record_end = 0; |
| } |
| else |
| { |
| /* In EXEC_REVERSE mode, this is the record_end of prev |
| instruction. |
| In EXEC_FORWARD mode, this is the record_end of current |
| instruction. */ |
| /* step */ |
| if (record_resume_step) |
| { |
| if (record_debug > 1) |
| { |
| fprintf_unfiltered (gdb_stdlog, |
| "Process record: step.\n"); |
| } |
| continue_flag = 0; |
| } |
| |
| /* check breakpoint */ |
| tmp_pc = regcache_read_pc (regcache); |
| if (breakpoint_inserted_here_p (tmp_pc)) |
| { |
| if (record_debug) |
| { |
| fprintf_unfiltered (gdb_stdlog, |
| "Process record: break at 0x%s.\n", |
| paddr_nz (tmp_pc)); |
| } |
| if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) |
| && execution_direction == EXEC_FORWARD |
| && !record_resume_step) |
| { |
| regcache_write_pc (regcache, |
| tmp_pc + |
| gdbarch_decr_pc_after_break |
| (get_regcache_arch (regcache))); |
| } |
| continue_flag = 0; |
| } |
| } |
| if (execution_direction == EXEC_REVERSE) |
| { |
| need_dasm = record_list->u.need_dasm; |
| } |
| } |
| |
| next: |
| if (continue_flag) |
| { |
| if (execution_direction == EXEC_REVERSE) |
| { |
| if (record_list->prev) |
| record_list = record_list->prev; |
| } |
| else |
| { |
| if (record_list->next) |
| record_list = record_list->next; |
| } |
| } |
| } |
| while (continue_flag); |
| |
| if (sigaction (SIGALRM, &old_act, NULL)) |
| { |
| perror_with_name (_("Process record: sigaction failed")); |
| } |
| |
| replay_out: |
| if (record_get_sig) |
| { |
| status->value.sig = TARGET_SIGNAL_INT; |
| } |
| else |
| { |
| status->value.sig = TARGET_SIGNAL_TRAP; |
| } |
| |
| discard_cleanups (old_cleanups); |
| } |
| |
| return inferior_ptid; |
| } |
| |
| static void |
| record_disconnect (struct target_ops *target, char *args, int from_tty) |
| { |
| if (record_debug) |
| { |
| fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n"); |
| } |
| unpush_target (&record_ops); |
| target_disconnect (args, from_tty); |
| } |
| |
| static void |
| record_detach (struct target_ops *ops, char *args, int from_tty) |
| { |
| if (record_debug) |
| { |
| fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n"); |
| } |
| unpush_target (&record_ops); |
| target_detach (args, from_tty); |
| } |
| |
| static void |
| record_mourn_inferior (struct target_ops *ops) |
| { |
| if (record_debug) |
| { |
| fprintf_unfiltered (gdb_stdlog, "Process record: record_mourn_inferior\n"); |
| } |
| unpush_target (&record_ops); |
| target_mourn_inferior (); |
| } |
| |
| /* Close process record target before killing the inferior process. */ |
| static void |
| record_kill (void) |
| { |
| if (record_debug) |
| { |
| fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); |
| } |
| unpush_target (&record_ops); |
| target_kill (); |
| } |
| |
| /* Record registers change (by user or by GDB) to list as an instruction. */ |
| static void |
| record_registers_change (struct regcache *regcache, int regnum) |
| { |
| /* Check record_insn_num. */ |
| record_check_insn_num (0); |
| |
| record_arch_list_head = NULL; |
| record_arch_list_tail = NULL; |
| |
| record_regcache = get_current_regcache (); |
| |
| if (regnum < 0) |
| { |
| int i; |
| for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) |
| { |
| if (record_arch_list_add_reg (i)) |
| { |
| record_list_release (record_arch_list_tail); |
| error (_("Process record: failed to record execution log.")); |
| } |
| } |
| } |
| else |
| { |
| if (record_arch_list_add_reg (regnum)) |
| { |
| record_list_release (record_arch_list_tail); |
| error (_("Process record: failed to record execution log.")); |
| } |
| } |
| if (record_arch_list_add_end (0)) |
| { |
| record_list_release (record_arch_list_tail); |
| error (_("Process record: failed to record execution log.")); |
| } |
| record_list->next = record_arch_list_head; |
| record_arch_list_head->prev = record_list; |
| record_list = record_arch_list_tail; |
| |
| if (record_insn_num == record_insn_max_num && record_insn_max_num) |
| { |
| record_list_release_first (); |
| } |
| else |
| { |
| record_insn_num++; |
| } |
| } |
| |
| static void |
| record_store_registers (struct regcache *regcache, int regno) |
| { |
| if (!in_record_wait) |
| { |
| if (RECORD_IS_REPLAY) |
| { |
| int n; |
| struct cleanup *old_cleanups; |
| |
| /* Let user choose if he wants to write register or not. */ |
| if (regno < 0) |
| { |
| n = |
| nquery (_ |
| ("Because GDB is in replay mode, changing the value of a register will make the execution log unusable from this point onward. Change all registers?")); |
| } |
| else |
| { |
| n = |
| nquery (_ |
| ("Because GDB is in replay mode, changing the value of a register will make the execution log unusable from this point onward. Change register %s?"), |
| gdbarch_register_name (get_regcache_arch (regcache), |
| regno)); |
| } |
| |
| if (!n) |
| { |
| /* Invalidate the value of regcache that was set in function |
| "regcache_raw_write". */ |
| if (regno < 0) |
| { |
| int i; |
| for (i = 0; |
| i < gdbarch_num_regs (get_regcache_arch (regcache)); |
| i++) |
| { |
| regcache_invalidate (regcache, i); |
| } |
| } |
| else |
| { |
| regcache_invalidate (regcache, regno); |
| } |
| |
| error (_("Process record canceled the operation.")); |
| } |
| |
| /* Destroy the record from here forward. */ |
| record_list_release_next (); |
| } |
| |
| record_registers_change (regcache, regno); |
| } |
| record_beneath_to_store_registers (regcache, regno); |
| } |
| |
| /* record_xfer_partial -- behavior is conditional on RECORD_IS_REPLAY. |
| In replay mode, we cannot write memory unles we are willing to |
| invalidate the record/replay log from this point forward. */ |
| |
| static LONGEST |
| record_xfer_partial (struct target_ops *ops, enum target_object object, |
| const char *annex, gdb_byte * readbuf, |
| const gdb_byte * writebuf, ULONGEST offset, LONGEST len) |
| { |
| if (!in_record_wait |
| && (object == TARGET_OBJECT_MEMORY |
| || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) |
| { |
| if (RECORD_IS_REPLAY) |
| { |
| /* Let user choose if he wants to write memory or not. */ |
| if (!nquery (_("Because GDB is in replay mode, writing to memory will make the execution log unusable from this point onward. Write memory at address 0x%s?"), |
| paddr_nz (offset))) |
| { |
| return -1; |
| } |
| |
| /* Destroy the record from here forward. */ |
| record_list_release_next (); |
| } |
| |
| /* Check record_insn_num */ |
| record_check_insn_num (0); |
| |
| /* Record registers change to list as an instruction. */ |
| record_arch_list_head = NULL; |
| record_arch_list_tail = NULL; |
| if (record_arch_list_add_mem (offset, len)) |
| { |
| record_list_release (record_arch_list_tail); |
| if (record_debug) |
| { |
| fprintf_unfiltered (gdb_stdlog, |
| _ |
| ("Process record: failed to record execution log.")); |
| } |
| return -1; |
| } |
| if (record_arch_list_add_end (0)) |
| { |
| record_list_release (record_arch_list_tail); |
| if (record_debug) |
| { |
| fprintf_unfiltered (gdb_stdlog, |
| _ |
| ("Process record: failed to record execution log.")); |
| } |
| return -1; |
| } |
| record_list->next = record_arch_list_head; |
| record_arch_list_head->prev = record_list; |
| record_list = record_arch_list_tail; |
| |
| if (record_insn_num == record_insn_max_num && record_insn_max_num) |
| { |
| record_list_release_first (); |
| } |
| else |
| { |
| record_insn_num++; |
| } |
| } |
| return record_beneath_to_xfer_partial (ops, object, annex, readbuf, |
| writebuf, offset, len); |
| } |
| |
| /* record_insert_breakpoint |
| record_remove_breakpoint |
| Behavior is conditional on TARGET_IS_PROCESS_RECORD. |
| We will not actually insert or remove breakpoints when replaying, |
| nor when recording. */ |
| |
| static int |
| record_insert_breakpoint (struct bp_target_info *bp_tgt) |
| { |
| if (!TARGET_IS_PROCESS_RECORD) |
| { |
| return record_beneath_to_insert_breakpoint (bp_tgt); |
| } |
| |
| return 0; |
| } |
| |
| static int |
| record_remove_breakpoint (struct bp_target_info *bp_tgt) |
| { |
| if (!TARGET_IS_PROCESS_RECORD) |
| { |
| return record_beneath_to_remove_breakpoint (bp_tgt); |
| } |
| |
| return 0; |
| } |
| |
| static int |
| record_can_execute_reverse (void) |
| { |
| return 1; |
| } |
| |
| static void |
| init_record_ops (void) |
| { |
| record_ops.to_shortname = "record"; |
| record_ops.to_longname = "Process record and replay target"; |
| record_ops.to_doc = |
| "Log program while executing and replay execution from log."; |
| record_ops.to_open = record_open; |
| record_ops.to_close = record_close; |
| record_ops.to_resume = record_resume; |
| record_ops.to_wait = record_wait; |
| record_ops.to_disconnect = record_disconnect; |
| record_ops.to_detach = record_detach; |
| record_ops.to_mourn_inferior = record_mourn_inferior; |
| record_ops.to_kill = record_kill; |
| record_ops.to_create_inferior = find_default_create_inferior; /* Make record support command "run". */ |
| record_ops.to_store_registers = record_store_registers; |
| record_ops.to_xfer_partial = record_xfer_partial; |
| record_ops.to_insert_breakpoint = record_insert_breakpoint; |
| record_ops.to_remove_breakpoint = record_remove_breakpoint; |
| record_ops.to_can_execute_reverse = record_can_execute_reverse; |
| record_ops.to_stratum = record_stratum; |
| record_ops.to_magic = OPS_MAGIC; |
| } |
| |
| static void |
| show_record_debug (struct ui_file *file, int from_tty, |
| struct cmd_list_element *c, const char *value) |
| { |
| fprintf_filtered (file, _("Debugging of process record target is %s.\n"), |
| value); |
| } |
| |
| /* cmd_record_start -- alias for "target record". */ |
| |
| static void |
| cmd_record_start (char *args, int from_tty) |
| { |
| execute_command ("target record", from_tty); |
| } |
| |
| /* cmd_record_delete -- truncate the record log from the present point |
| of replay until the end. */ |
| |
| static void |
| cmd_record_delete (char *args, int from_tty) |
| { |
| if (TARGET_IS_PROCESS_RECORD) |
| { |
| if (RECORD_IS_REPLAY) |
| { |
| if (!from_tty || query (_("Delete the log from this point forward and begin to record the running message at current PC?"))) |
| { |
| record_list_release_next (); |
| } |
| } |
| else |
| { |
| printf_unfiltered (_("Already at end of record list.\n")); |
| } |
| |
| } |
| else |
| { |
| printf_unfiltered (_("Process record is not started.\n")); |
| } |
| } |
| |
| /* cmd_record_stop -- implement the "stoprecord" command. */ |
| |
| static void |
| cmd_record_stop (char *args, int from_tty) |
| { |
| if (TARGET_IS_PROCESS_RECORD) |
| { |
| if (!record_list || !from_tty || query (_("Delete recorded log and stop recording?"))) |
| { |
| unpush_target (&record_ops); |
| } |
| } |
| else |
| { |
| printf_unfiltered (_("Process record is not started.\n")); |
| } |
| } |
| |
| /* set_record_insn_max_num -- set upper limit of record log size. */ |
| |
| static void |
| set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c) |
| { |
| if (record_insn_num > record_insn_max_num && record_insn_max_num) |
| { |
| printf_unfiltered (_("Record instructions number is bigger than record instructions max number. Auto delete the first ones?\n")); |
| |
| while (record_insn_num > record_insn_max_num) |
| { |
| record_list_release_first (); |
| } |
| } |
| } |
| |
| /* show_record_insn_number -- print the current index |
| into the record log (number of insns recorded so far). */ |
| |
| static void |
| show_record_insn_number (char *ignore, int from_tty) |
| { |
| printf_unfiltered (_("Record instruction number is %d.\n"), |
| record_insn_num); |
| } |
| |
| void |
| _initialize_record (void) |
| { |
| /* Init record_maskall. */ |
| if (sigfillset (&record_maskall) == -1) |
| { |
| perror_with_name (_("Process record: sigfillset failed")); |
| } |
| |
| /* Init record_first. */ |
| record_first.prev = NULL; |
| record_first.next = NULL; |
| record_first.type = record_end; |
| record_first.u.need_dasm = 0; |
| |
| init_record_ops (); |
| add_target (&record_ops); |
| |
| add_setshow_zinteger_cmd ("record", no_class, &record_debug, |
| _("Set debugging of record/replay feature."), |
| _("Show debugging of record/replay feature."), |
| _ |
| ("When enabled, debugging output for record/replay feature is displayed."), |
| NULL, show_record_debug, &setdebuglist, |
| &showdebuglist); |
| |
| add_com ("record", class_obscure, cmd_record_start, |
| _("Abbreviated form of \"target record\" command.")); |
| |
| add_com_alias ("rec", "record", class_obscure, 1); |
| |
| /* XXX: I try to use some simple commands such as "disconnect" and |
| "detach" to support this functions. But these commands all have |
| other affect to GDB such as call function "no_shared_libraries". |
| So I add special commands to GDB. */ |
| add_com ("delrecord", class_obscure, cmd_record_delete, |
| _("Delete the rest of execution log and start recording it anew.")); |
| add_com_alias ("dr", "delrecord", class_obscure, 1); |
| add_com ("stoprecord", class_obscure, cmd_record_stop, |
| _("Stop the record/replay target.")); |
| add_com_alias ("sr", "stoprecord", class_obscure, 1); |
| |
| /* Record instructions number limit command. */ |
| add_setshow_boolean_cmd ("record-stop-at-limit", no_class, |
| &record_stop_at_limit, |
| _("Set whether record/replay stop when record/replay buffer becomes full."), |
| _("Show whether record/replay stop when record/replay buffer becomes full."), _("\ |
| Enable is default value.\n\ |
| When enabled, if the record/replay buffer becomes full,\n\ |
| ask user what to do.\n\ |
| When disabled, if the record/replay buffer becomes full,\n\ |
| delete it and start new recording."), NULL, NULL, &setlist, &showlist); |
| add_setshow_zinteger_cmd ("record-insn-number-max", no_class, |
| &record_insn_max_num, |
| _("Set record/replay buffer limit."), |
| _("Show record/replay buffer limit."), _("\ |
| Set the maximum number of instructions to be stored in the\n\ |
| record/replay buffer. Zero means unlimited (default 200000)."), |
| set_record_insn_max_num, |
| NULL, &setlist, &showlist); |
| add_info ("record-insn-number", show_record_insn_number, _("\ |
| Show the current number of instructions in the record/replay buffer.")); |
| } |