blob: 1cde50e9f37fd79dd8085ec44241c224a42b56fa [file] [log] [blame]
/*
* 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, 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. */
}
}
}