| /* |
| * replay-debugging.c |
| * |
| * Copyright (c) 2010-2020 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/osdep.h" |
| #include "qapi/error.h" |
| #include "sysemu/replay.h" |
| #include "sysemu/runstate.h" |
| #include "replay-internal.h" |
| #include "monitor/hmp.h" |
| #include "monitor/monitor.h" |
| #include "qapi/qapi-commands-replay.h" |
| #include "qapi/qmp/qdict.h" |
| #include "qemu/timer.h" |
| #include "block/snapshot.h" |
| #include "migration/snapshot.h" |
| |
| static bool replay_is_debugging; |
| static int64_t replay_last_breakpoint; |
| static int64_t replay_last_snapshot; |
| |
| bool replay_running_debug(void) |
| { |
| return replay_is_debugging; |
| } |
| |
| void hmp_info_replay(Monitor *mon, const QDict *qdict) |
| { |
| if (replay_mode == REPLAY_MODE_NONE) { |
| monitor_printf(mon, "Record/replay is not active\n"); |
| } else { |
| monitor_printf(mon, |
| "%s execution '%s': instruction count = %"PRId64"\n", |
| replay_mode == REPLAY_MODE_RECORD ? "Recording" : "Replaying", |
| replay_get_filename(), replay_get_current_icount()); |
| } |
| } |
| |
| ReplayInfo *qmp_query_replay(Error **errp) |
| { |
| ReplayInfo *retval = g_new0(ReplayInfo, 1); |
| |
| retval->mode = replay_mode; |
| if (replay_get_filename()) { |
| retval->filename = g_strdup(replay_get_filename()); |
| } |
| retval->icount = replay_get_current_icount(); |
| return retval; |
| } |
| |
| static void replay_break(uint64_t icount, QEMUTimerCB callback, void *opaque) |
| { |
| assert(replay_mode == REPLAY_MODE_PLAY); |
| assert(replay_mutex_locked()); |
| assert(replay_break_icount >= replay_get_current_icount()); |
| assert(callback); |
| |
| replay_break_icount = icount; |
| |
| if (replay_break_timer) { |
| timer_del(replay_break_timer); |
| } |
| replay_break_timer = timer_new_ns(QEMU_CLOCK_REALTIME, |
| callback, opaque); |
| } |
| |
| static void replay_delete_break(void) |
| { |
| assert(replay_mode == REPLAY_MODE_PLAY); |
| assert(replay_mutex_locked()); |
| |
| if (replay_break_timer) { |
| timer_free(replay_break_timer); |
| replay_break_timer = NULL; |
| } |
| replay_break_icount = -1ULL; |
| } |
| |
| static void replay_stop_vm(void *opaque) |
| { |
| vm_stop(RUN_STATE_PAUSED); |
| replay_delete_break(); |
| } |
| |
| void qmp_replay_break(int64_t icount, Error **errp) |
| { |
| if (replay_mode == REPLAY_MODE_PLAY) { |
| if (icount >= replay_get_current_icount()) { |
| replay_break(icount, replay_stop_vm, NULL); |
| } else { |
| error_setg(errp, |
| "cannot set breakpoint at the instruction in the past"); |
| } |
| } else { |
| error_setg(errp, "setting the breakpoint is allowed only in play mode"); |
| } |
| } |
| |
| void hmp_replay_break(Monitor *mon, const QDict *qdict) |
| { |
| int64_t icount = qdict_get_try_int(qdict, "icount", -1LL); |
| Error *err = NULL; |
| |
| qmp_replay_break(icount, &err); |
| if (err) { |
| error_report_err(err); |
| return; |
| } |
| } |
| |
| void qmp_replay_delete_break(Error **errp) |
| { |
| if (replay_mode == REPLAY_MODE_PLAY) { |
| replay_delete_break(); |
| } else { |
| error_setg(errp, "replay breakpoints are allowed only in play mode"); |
| } |
| } |
| |
| void hmp_replay_delete_break(Monitor *mon, const QDict *qdict) |
| { |
| Error *err = NULL; |
| |
| qmp_replay_delete_break(&err); |
| if (err) { |
| error_report_err(err); |
| return; |
| } |
| } |
| |
| static char *replay_find_nearest_snapshot(int64_t icount, |
| int64_t *snapshot_icount) |
| { |
| BlockDriverState *bs; |
| QEMUSnapshotInfo *sn_tab; |
| QEMUSnapshotInfo *nearest = NULL; |
| char *ret = NULL; |
| int rv; |
| int nb_sns, i; |
| |
| *snapshot_icount = -1; |
| |
| bs = bdrv_all_find_vmstate_bs(NULL, false, NULL, NULL); |
| if (!bs) { |
| goto fail; |
| } |
| |
| nb_sns = bdrv_snapshot_list(bs, &sn_tab); |
| |
| for (i = 0; i < nb_sns; i++) { |
| rv = bdrv_all_has_snapshot(sn_tab[i].name, false, NULL, NULL); |
| if (rv < 0) |
| goto fail; |
| if (rv == 1) { |
| if (sn_tab[i].icount != -1ULL |
| && sn_tab[i].icount <= icount |
| && (!nearest || nearest->icount < sn_tab[i].icount)) { |
| nearest = &sn_tab[i]; |
| } |
| } |
| } |
| if (nearest) { |
| ret = g_strdup(nearest->name); |
| *snapshot_icount = nearest->icount; |
| } |
| g_free(sn_tab); |
| |
| fail: |
| return ret; |
| } |
| |
| static void replay_seek(int64_t icount, QEMUTimerCB callback, Error **errp) |
| { |
| char *snapshot = NULL; |
| int64_t snapshot_icount; |
| |
| if (replay_mode != REPLAY_MODE_PLAY) { |
| error_setg(errp, "replay must be enabled to seek"); |
| return; |
| } |
| |
| snapshot = replay_find_nearest_snapshot(icount, &snapshot_icount); |
| if (snapshot) { |
| if (icount < replay_get_current_icount() |
| || replay_get_current_icount() < snapshot_icount) { |
| vm_stop(RUN_STATE_RESTORE_VM); |
| load_snapshot(snapshot, NULL, false, NULL, errp); |
| } |
| g_free(snapshot); |
| } |
| if (replay_get_current_icount() <= icount) { |
| replay_break(icount, callback, NULL); |
| vm_start(); |
| } else { |
| error_setg(errp, "cannot seek to the specified instruction count"); |
| } |
| } |
| |
| void qmp_replay_seek(int64_t icount, Error **errp) |
| { |
| replay_seek(icount, replay_stop_vm, errp); |
| } |
| |
| void hmp_replay_seek(Monitor *mon, const QDict *qdict) |
| { |
| int64_t icount = qdict_get_try_int(qdict, "icount", -1LL); |
| Error *err = NULL; |
| |
| qmp_replay_seek(icount, &err); |
| if (err) { |
| error_report_err(err); |
| return; |
| } |
| } |
| |
| static void replay_stop_vm_debug(void *opaque) |
| { |
| replay_is_debugging = false; |
| vm_stop(RUN_STATE_DEBUG); |
| replay_delete_break(); |
| } |
| |
| bool replay_reverse_step(void) |
| { |
| Error *err = NULL; |
| |
| assert(replay_mode == REPLAY_MODE_PLAY); |
| |
| if (replay_get_current_icount() != 0) { |
| replay_seek(replay_get_current_icount() - 1, |
| replay_stop_vm_debug, &err); |
| if (err) { |
| error_free(err); |
| return false; |
| } |
| replay_is_debugging = true; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static void replay_continue_end(void) |
| { |
| replay_is_debugging = false; |
| vm_stop(RUN_STATE_DEBUG); |
| replay_delete_break(); |
| } |
| |
| static void replay_continue_stop(void *opaque) |
| { |
| Error *err = NULL; |
| if (replay_last_breakpoint != -1LL) { |
| replay_seek(replay_last_breakpoint, replay_stop_vm_debug, &err); |
| if (err) { |
| error_free(err); |
| replay_continue_end(); |
| } |
| return; |
| } |
| /* |
| * No breakpoints since the last snapshot. |
| * Find previous snapshot and try again. |
| */ |
| if (replay_last_snapshot != 0) { |
| replay_seek(replay_last_snapshot - 1, replay_continue_stop, &err); |
| if (err) { |
| error_free(err); |
| replay_continue_end(); |
| } |
| replay_last_snapshot = replay_get_current_icount(); |
| } else { |
| /* Seek to the very first step */ |
| replay_seek(0, replay_stop_vm_debug, &err); |
| if (err) { |
| error_free(err); |
| replay_continue_end(); |
| } |
| } |
| } |
| |
| bool replay_reverse_continue(void) |
| { |
| Error *err = NULL; |
| |
| assert(replay_mode == REPLAY_MODE_PLAY); |
| |
| if (replay_get_current_icount() != 0) { |
| replay_seek(replay_get_current_icount() - 1, |
| replay_continue_stop, &err); |
| if (err) { |
| error_free(err); |
| return false; |
| } |
| replay_last_breakpoint = -1LL; |
| replay_is_debugging = true; |
| replay_last_snapshot = replay_get_current_icount(); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void replay_breakpoint(void) |
| { |
| assert(replay_mode == REPLAY_MODE_PLAY); |
| replay_last_breakpoint = replay_get_current_icount(); |
| } |
| |
| void replay_gdb_attached(void) |
| { |
| /* |
| * Create VM snapshot on temporary overlay to allow reverse |
| * debugging even if snapshots were not enabled. |
| */ |
| if (replay_mode == REPLAY_MODE_PLAY |
| && !replay_snapshot) { |
| if (!save_snapshot("start_debugging", true, NULL, false, NULL, NULL)) { |
| /* Can't create the snapshot. Continue conventional debugging. */ |
| } |
| } |
| } |