|  | /* | 
|  | * 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 "system/replay.h" | 
|  | #include "system/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. */ | 
|  | } | 
|  | } | 
|  | } |