This patch introduces support of reverse debugging through the gdb remote protocol. Patch adds reverse-stepi and reverse-continue commands support to qemu. Other reverse commands should also work, because they reuse these ones.
Signed-off-by: Pavel Dovgalyuk <pavel.dovga...@ispras.ru> --- exec.c | 7 ++ gdbstub.c | 79 +++++++++++++++++----- qapi-schema.json | 2 - replay/Makefile.objs | 1 replay/replay-debug.c | 154 +++++++++++++++++++++++++++++++++++++++++++ replay/replay-internal.h | 4 + replay/replay.c | 3 + replay/replay.h | 11 +++ target-arm/helper.h | 1 target-arm/replay_helper.c | 5 + target-arm/translate.c | 14 +++- target-i386/helper.h | 1 target-i386/replay_helper.c | 5 + target-i386/translate.c | 12 ++- 14 files changed, 272 insertions(+), 27 deletions(-) create mode 100755 replay/replay-debug.c diff --git a/exec.c b/exec.c index 8403cc5..f04ca43 100644 --- a/exec.c +++ b/exec.c @@ -1647,6 +1647,13 @@ static void check_watchpoint(int offset, int len_mask, int flags) QTAILQ_FOREACH(wp, &cpu->watchpoints, entry) { if ((vaddr == (wp->vaddr & len_mask) || (vaddr & wp->len_mask) == wp->vaddr) && (wp->flags & flags)) { + /* Don't actually process a watchpoint, it will be processed, + when reverse execution stops. */ + if (replay_get_play_submode() == REPLAY_SUBMODE_REVERSE) { + wp->flags &= ~BP_WATCHPOINT_HIT; + replay_reverse_breakpoint(); + continue; + } wp->flags |= BP_WATCHPOINT_HIT; if (!cpu->watchpoint_hit) { cpu->watchpoint_hit = wp; diff --git a/gdbstub.c b/gdbstub.c index 8afe0b7..228a36f 100644 --- a/gdbstub.c +++ b/gdbstub.c @@ -40,6 +40,7 @@ #include "cpu.h" #include "qemu/sockets.h" #include "sysemu/kvm.h" +#include "replay/replay.h" static inline int target_memory_rw_debug(CPUState *cpu, target_ulong addr, uint8_t *buf, int len, bool is_write) @@ -313,6 +314,19 @@ typedef struct GDBState { */ static int sstep_flags = SSTEP_ENABLE|SSTEP_NOIRQ|SSTEP_NOTIMER; +/*! Retrieves flags for single step mode. */ +static int get_sstep_flags(void) +{ + /* In replay mode all events written into the log should be replayed. + * That is why NOIRQ flag is removed in this mode. + */ + if (replay_mode != REPLAY_MODE_NONE) { + return SSTEP_ENABLE; + } else { + return sstep_flags; + } +} + static GDBState *gdbserver_state; bool gdb_has_xml; @@ -835,7 +849,7 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) s->c_cpu = cpu; } if (res == 's') { - cpu_single_step(s->c_cpu, sstep_flags); + cpu_single_step(s->c_cpu, get_sstep_flags()); } s->signal = res_signal; gdb_continue(s); @@ -863,9 +877,29 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) addr = strtoull(p, (char **)&p, 16); gdb_set_cpu_pc(s, addr); } - cpu_single_step(s->c_cpu, sstep_flags); + cpu_single_step(s->c_cpu, get_sstep_flags()); gdb_continue(s); return RS_IDLE; + case 'b': + /* backward debugging commands */ + if (replay_mode == REPLAY_MODE_PLAY + && replay_get_play_submode() == REPLAY_SUBMODE_NORMAL) { + switch (*p) { + case 's': + replay_reverse_step(); + gdb_continue(s); + return RS_IDLE; + case 'c': + replay_reverse_continue(); + gdb_continue(s); + return RS_IDLE; + default: + goto unknown_command; + } + } else { + put_packet(s, "E22"); + } + goto unknown_command; case 'F': { target_ulong ret; @@ -937,8 +971,6 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) if (target_memory_rw_debug(s->g_cpu, addr, mem_buf, len, true) != 0) { put_packet(s, "E14"); - } else { - put_packet(s, "OK"); } break; case 'p': @@ -1035,18 +1067,23 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) put_packet(s, buf); break; } else if (strncmp(p,"qemu.sstep",10) == 0) { - /* Display or change the sstep_flags */ - p += 10; - if (*p != '=') { - /* Display current setting */ - snprintf(buf, sizeof(buf), "0x%x", sstep_flags); - put_packet(s, buf); - break; + if (replay_mode == REPLAY_MODE_NONE) { + /* Display or change the sstep_flags */ + p += 10; + if (*p != '=') { + /* Display current setting */ + snprintf(buf, sizeof(buf), "0x%x", sstep_flags); + put_packet(s, buf); + break; + } + p++; + type = strtoul(p, (char **)&p, 16); + sstep_flags = type; + put_packet(s, "OK"); + } else { + /* Cannot change sstep flags in replay mode */ + put_packet(s, "E22"); } - p++; - type = strtoul(p, (char **)&p, 16); - sstep_flags = type; - put_packet(s, "OK"); break; } else if (strcmp(p,"C") == 0) { /* "Current thread" remains vague in the spec, so always return @@ -1113,6 +1150,9 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) if (cc->gdb_core_xml_file != NULL) { pstrcat(buf, sizeof(buf), ";qXfer:features:read+"); } + if (replay_mode == REPLAY_MODE_PLAY) { + pstrcat(buf, sizeof(buf), ";ReverseStep+;ReverseContinue+"); + } put_packet(s, buf); break; } @@ -1174,8 +1214,13 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) void gdb_set_stop_cpu(CPUState *cpu) { - gdbserver_state->c_cpu = cpu; - gdbserver_state->g_cpu = cpu; + /* DEBUG interrupts are also used by replay module. + In some cases gdb is not connected, when replay is used. + This check is added to prevent faults in such cases. */ + if (gdbserver_state) { + gdbserver_state->c_cpu = cpu; + gdbserver_state->g_cpu = cpu; + } } #ifndef CONFIG_USER_ONLY diff --git a/qapi-schema.json b/qapi-schema.json index d8755a3..d71fe76 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -3522,7 +3522,7 @@ # Since: 2.2 ## { 'enum': 'ReplaySubmode', - 'data': [ 'unknown', 'normal' ] } + 'data': [ 'unknown', 'normal', 'reverse' ] } ## # @ReplayInfo: diff --git a/replay/Makefile.objs b/replay/Makefile.objs index 62eba1b..cb99839 100755 --- a/replay/Makefile.objs +++ b/replay/Makefile.objs @@ -9,3 +9,4 @@ obj-y += replay-audio.o obj-y += replay-char.o obj-y += replay-usb.o obj-y += replay-qmp.o +obj-y += replay-debug.o diff --git a/replay/replay-debug.c b/replay/replay-debug.c new file mode 100755 index 0000000..8785e68 --- /dev/null +++ b/replay/replay-debug.c @@ -0,0 +1,154 @@ +/* + * replay-debug.c + * + * Copyright (c) 2010-2014 Institute for System Programming + * of the Russian Academy of Sciences. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#include "qemu-common.h" +#include "exec/cpu-common.h" +#include "exec/cpu-defs.h" + +#include "replay.h" +#include "replay-internal.h" + +/* Reverse debugging data */ + +/* Saved handler of the debug exception */ +static CPUDebugExcpHandler *prev_debug_excp_handler; +/* Step of the last breakpoint hit. + Used for seeking in reverse continue mode. */ +static uint64_t last_breakpoint_step; +/* Start step, where reverse continue begins, + or target step for reverse stepping.*/ +static uint64_t last_reverse_step; +/* Start step, where reverse continue begins.*/ +static uint64_t start_reverse_step; +/* Previously loaded step for reverse continue */ +static SavedStateInfo *reverse_state; + +/*! Breakpoint handler for pass2 of reverse continue. + Stops the execution at previously saved breakpoint step. */ +static void reverse_continue_pass2_breakpoint_handler(CPUArchState *env) +{ + if (replay_get_current_step() == last_breakpoint_step) { + CPUState *cpu = ENV_GET_CPU(env); + CPUDebugExcpHandler *handler = prev_debug_excp_handler; + prev_debug_excp_handler = NULL; + + play_submode = REPLAY_SUBMODE_NORMAL; + + cpu->exception_index = EXCP_DEBUG; + /* invoke the breakpoint */ + cpu_set_debug_excp_handler(handler); + handler(env); + cpu_exit(cpu); + } +} + +/*! Breakpoint handler for pass1 of reverse continue. + Saves last breakpoint hit and switches to pass2 + when starting point is reached. */ +static void reverse_continue_pass1_breakpoint_handler(CPUArchState *env) +{ + if (replay_get_current_step() == last_reverse_step) { + CPUState *cpu = ENV_GET_CPU(env); + /* repeat first pass if breakpoint was not found + on current iteration */ + if (last_breakpoint_step == reverse_state->step - 1 + && reverse_state != saved_states) { + last_reverse_step = reverse_state->step; + /* load previous state */ + --reverse_state; + last_breakpoint_step = reverse_state->step - 1; + replay_seek_step(reverse_state->step); + /* set break should be after seek, because seek resets break */ + replay_set_break(last_reverse_step); + cpu_loop_exit(cpu); + } else { + /* this condition is needed, when no breakpoints were found */ + if (last_breakpoint_step == reverse_state->step - 1) { + ++last_breakpoint_step; + } + cpu_set_debug_excp_handler( + reverse_continue_pass2_breakpoint_handler); + + reverse_continue_pass2_breakpoint_handler(env); + replay_seek_step(last_breakpoint_step); + cpu_loop_exit(cpu); + } + } else { + /* skip watchpoint/breakpoint at the current step + to allow reverse continue */ + last_breakpoint_step = replay_get_current_step(); + } +} + +void replay_reverse_breakpoint(void) +{ + /* we started reverse execution from a breakpoint */ + if (replay_get_current_step() != start_reverse_step) { + last_breakpoint_step = replay_get_current_step(); + } +} + +void replay_reverse_continue(void) +{ + if (replay_mode == REPLAY_MODE_PLAY + && play_submode == REPLAY_SUBMODE_NORMAL) { + tb_flush_all(); + play_submode = REPLAY_SUBMODE_REVERSE; + + last_reverse_step = replay_get_current_step(); + start_reverse_step = replay_get_current_step(); + /* load initial state */ + reverse_state = find_nearest_state(replay_get_current_step()); + replay_seek_step(reverse_state->step); + /* run to current step */ + replay_set_break(last_reverse_step); + /* decrement to allow breaking at the first step */ + last_breakpoint_step = reverse_state->step - 1; + prev_debug_excp_handler = + cpu_set_debug_excp_handler( + reverse_continue_pass1_breakpoint_handler); + } +} + +/*! Breakpoint handler for reverse stepping. + Stops at the desired step and skips other breakpoints. */ +static void reverse_step_breakpoint_handler(CPUArchState *env) +{ + if (replay_get_current_step() == last_reverse_step) { + CPUState *cpu = ENV_GET_CPU(env); + CPUDebugExcpHandler *handler = prev_debug_excp_handler; + prev_debug_excp_handler = NULL; + + play_submode = REPLAY_SUBMODE_NORMAL; + + cpu->exception_index = EXCP_DEBUG; + /* invoke the breakpoint */ + cpu_set_debug_excp_handler(handler); + handler(env); + cpu_exit(cpu); + } +} + +void replay_reverse_step(void) +{ + if (replay_mode == REPLAY_MODE_PLAY + && play_submode == REPLAY_SUBMODE_NORMAL + && replay_get_current_step() > 0) { + tb_flush_all(); + play_submode = REPLAY_SUBMODE_REVERSE; + + last_reverse_step = replay_get_current_step() - 1; + replay_seek_step(last_reverse_step); + + prev_debug_excp_handler = + cpu_set_debug_excp_handler(reverse_step_breakpoint_handler); + } +} diff --git a/replay/replay-internal.h b/replay/replay-internal.h index e529e9a..ec42a94 100755 --- a/replay/replay-internal.h +++ b/replay/replay-internal.h @@ -88,7 +88,11 @@ struct SavedStateInfo { }; /*! Reference to the saved state */ typedef struct SavedStateInfo SavedStateInfo; +/* List of the saved states information */ +extern SavedStateInfo *saved_states; +/*! Stores current submode for PLAY mode */ +extern ReplaySubmode play_submode; extern volatile unsigned int replay_data_kind; extern volatile unsigned int replay_has_unread_data; diff --git a/replay/replay.c b/replay/replay.c index eaaee78..651653c 100755 --- a/replay/replay.c +++ b/replay/replay.c @@ -295,6 +295,9 @@ void replay_instruction(int process_events) first_cpu->exception_index = EXCP_DEBUG; monitor_printf(default_mon, "Execution has stopped.\n"); vm_stop(EXCP_DEBUG); + } else if (play_submode == REPLAY_SUBMODE_REVERSE) { + cpu_handle_debug_exception(first_cpu->env_ptr); + return; } /* for breaking execution loop */ cpu_exit(first_cpu); diff --git a/replay/replay.h b/replay/replay.h index 70f8d84..58beed8 100755 --- a/replay/replay.h +++ b/replay/replay.h @@ -184,6 +184,17 @@ void replay_req_complete_iso(struct libusb_transfer *xfer); to be completed by reading it from the log */ void replay_req_register_iso(struct libusb_transfer *xfer); +/* Reverse debugging */ + +/*! Initializes reverse continue execution. + Called by debugger module. */ +void replay_reverse_continue(void); +/*! Initializes reverse step execution. + Called by debugger module. */ +void replay_reverse_step(void); +/* Called instead of invoking breakpoint in reverse continue mode. */ +void replay_reverse_breakpoint(void); + /* Other data */ /*! Writes or reads integer value to/from replay log. */ diff --git a/target-arm/helper.h b/target-arm/helper.h index 0233b64..802ff9d 100644 --- a/target-arm/helper.h +++ b/target-arm/helper.h @@ -531,3 +531,4 @@ DEF_HELPER_FLAGS_2(neon_pmull_64_hi, TCG_CALL_NO_RWG_SE, i64, i64, i64) #endif DEF_HELPER_1(replay_instruction, i32, env) +DEF_HELPER_0(reverse_breakpoint, void) diff --git a/target-arm/replay_helper.c b/target-arm/replay_helper.c index 418d548..b529ffd 100755 --- a/target-arm/replay_helper.c +++ b/target-arm/replay_helper.c @@ -31,3 +31,8 @@ uint32_t helper_replay_instruction(CPUARMState *env) replay_instruction(timer); return timer; } + +void helper_reverse_breakpoint(void) +{ + replay_reverse_breakpoint(); +} diff --git a/target-arm/translate.c b/target-arm/translate.c index 3407ced..7aa9d1b 100644 --- a/target-arm/translate.c +++ b/target-arm/translate.c @@ -11021,11 +11021,15 @@ static inline void gen_intermediate_code_internal(ARMCPU *cpu, if (unlikely(!QTAILQ_EMPTY(&cs->breakpoints))) { QTAILQ_FOREACH(bp, &cs->breakpoints, entry) { if (bp->pc == dc->pc) { - gen_exception_internal_insn(dc, 0, EXCP_DEBUG); - /* Advance PC so that clearing the breakpoint will - invalidate this TB. */ - dc->pc += 2; - goto done_generating; + if (replay_get_play_submode() == REPLAY_SUBMODE_REVERSE) { + gen_helper_reverse_breakpoint(); + } else { + gen_exception_internal_insn(dc, 0, EXCP_DEBUG); + /* Advance PC so that clearing the breakpoint will + invalidate this TB. */ + dc->pc += 2; + goto done_generating; + } } } } diff --git a/target-i386/helper.h b/target-i386/helper.h index 058302b..9aacef1 100644 --- a/target-i386/helper.h +++ b/target-i386/helper.h @@ -219,3 +219,4 @@ DEF_HELPER_3(rcrq, tl, env, tl, tl) #endif DEF_HELPER_1(replay_instruction, i32, env) +DEF_HELPER_0(reverse_breakpoint, void) diff --git a/target-i386/replay_helper.c b/target-i386/replay_helper.c index 7e70c78..6c8d705 100755 --- a/target-i386/replay_helper.c +++ b/target-i386/replay_helper.c @@ -31,3 +31,8 @@ uint32_t helper_replay_instruction(CPUX86State *env) replay_instruction(timer); return timer; } + +void helper_reverse_breakpoint(void) +{ + replay_reverse_breakpoint(); +} diff --git a/target-i386/translate.c b/target-i386/translate.c index 1843b46..d0c04a7 100644 --- a/target-i386/translate.c +++ b/target-i386/translate.c @@ -2536,10 +2536,14 @@ static void gen_interrupt(DisasContext *s, int intno, static void gen_debug(DisasContext *s, target_ulong cur_eip) { - gen_update_cc_op(s); - gen_jmp_im(cur_eip); - gen_helper_debug(cpu_env); - s->is_jmp = DISAS_TB_JUMP; + if (replay_get_play_submode() == REPLAY_SUBMODE_REVERSE) { + gen_helper_reverse_breakpoint(); + } else { + gen_update_cc_op(s); + gen_jmp_im(cur_eip); + gen_helper_debug(cpu_env); + s->is_jmp = DISAS_TB_JUMP; + } } /* generate a generic end of block. Trace exception is also generated