/* * 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->has_filename = true; } 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; AioContext *aio_context; *snapshot_icount = -1; bs = bdrv_all_find_vmstate_bs(NULL, false, NULL, NULL); if (!bs) { goto fail; } aio_context = bdrv_get_aio_context(bs); aio_context_acquire(aio_context); nb_sns = bdrv_snapshot_list(bs, &sn_tab); aio_context_release(aio_context); 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, 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", NULL)) { /* Can't create the snapshot. Continue conventional debugging. */ } } }