aboutsummaryrefslogtreecommitdiffstats
path: root/replay
diff options
context:
space:
mode:
authorTimos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>2023-10-10 11:40:56 +0000
committerTimos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>2023-10-10 11:40:56 +0000
commite02cda008591317b1625707ff8e115a4841aa889 (patch)
treeaee302e3cf8b59ec2d32ec481be3d1afddfc8968 /replay
parentcc668e6b7e0ffd8c9d130513d12053cf5eda1d3b (diff)
Introduce Virtio-loopback epsilon release:
Epsilon release introduces a new compatibility layer which make virtio-loopback design to work with QEMU and rust-vmm vhost-user backend without require any changes. Signed-off-by: Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com> Change-Id: I52e57563e08a7d0bdc002f8e928ee61ba0c53dd9
Diffstat (limited to 'replay')
-rw-r--r--replay/meson.build13
-rw-r--r--replay/replay-audio.c72
-rw-r--r--replay/replay-char.c158
-rw-r--r--replay/replay-debugging.c334
-rw-r--r--replay/replay-events.c335
-rw-r--r--replay/replay-input.c140
-rw-r--r--replay/replay-internal.c285
-rw-r--r--replay/replay-internal.h198
-rw-r--r--replay/replay-net.c101
-rw-r--r--replay/replay-random.c44
-rw-r--r--replay/replay-snapshot.c100
-rw-r--r--replay/replay-time.c62
-rw-r--r--replay/replay.c403
-rw-r--r--replay/stubs-system.c96
14 files changed, 2341 insertions, 0 deletions
diff --git a/replay/meson.build b/replay/meson.build
new file mode 100644
index 000000000..21aefad22
--- /dev/null
+++ b/replay/meson.build
@@ -0,0 +1,13 @@
+softmmu_ss.add(when: 'CONFIG_TCG', if_true: files(
+ 'replay.c',
+ 'replay-internal.c',
+ 'replay-events.c',
+ 'replay-time.c',
+ 'replay-input.c',
+ 'replay-char.c',
+ 'replay-snapshot.c',
+ 'replay-net.c',
+ 'replay-audio.c',
+ 'replay-random.c',
+ 'replay-debugging.c',
+), if_false: files('stubs-system.c'))
diff --git a/replay/replay-audio.c b/replay/replay-audio.c
new file mode 100644
index 000000000..91854f02e
--- /dev/null
+++ b/replay/replay-audio.c
@@ -0,0 +1,72 @@
+/*
+ * replay-audio.c
+ *
+ * Copyright (c) 2010-2017 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 "qemu/error-report.h"
+#include "sysemu/replay.h"
+#include "replay-internal.h"
+#include "audio/audio.h"
+
+void replay_audio_out(size_t *played)
+{
+ if (replay_mode == REPLAY_MODE_RECORD) {
+ g_assert(replay_mutex_locked());
+ replay_save_instructions();
+ replay_put_event(EVENT_AUDIO_OUT);
+ replay_put_qword(*played);
+ } else if (replay_mode == REPLAY_MODE_PLAY) {
+ g_assert(replay_mutex_locked());
+ replay_account_executed_instructions();
+ if (replay_next_event_is(EVENT_AUDIO_OUT)) {
+ *played = replay_get_qword();
+ replay_finish_event();
+ } else {
+ error_report("Missing audio out event in the replay log");
+ abort();
+ }
+ }
+}
+
+void replay_audio_in(size_t *recorded, void *samples, size_t *wpos, size_t size)
+{
+ int pos;
+ uint64_t left, right;
+ if (replay_mode == REPLAY_MODE_RECORD) {
+ g_assert(replay_mutex_locked());
+ replay_save_instructions();
+ replay_put_event(EVENT_AUDIO_IN);
+ replay_put_qword(*recorded);
+ replay_put_qword(*wpos);
+ for (pos = (*wpos - *recorded + size) % size ; pos != *wpos
+ ; pos = (pos + 1) % size) {
+ audio_sample_to_uint64(samples, pos, &left, &right);
+ replay_put_qword(left);
+ replay_put_qword(right);
+ }
+ } else if (replay_mode == REPLAY_MODE_PLAY) {
+ g_assert(replay_mutex_locked());
+ replay_account_executed_instructions();
+ if (replay_next_event_is(EVENT_AUDIO_IN)) {
+ *recorded = replay_get_qword();
+ *wpos = replay_get_qword();
+ for (pos = (*wpos - *recorded + size) % size ; pos != *wpos
+ ; pos = (pos + 1) % size) {
+ left = replay_get_qword();
+ right = replay_get_qword();
+ audio_sample_from_uint64(samples, pos, left, right);
+ }
+ replay_finish_event();
+ } else {
+ error_report("Missing audio in event in the replay log");
+ abort();
+ }
+ }
+}
diff --git a/replay/replay-char.c b/replay/replay-char.c
new file mode 100644
index 000000000..dc0002367
--- /dev/null
+++ b/replay/replay-char.c
@@ -0,0 +1,158 @@
+/*
+ * replay-char.c
+ *
+ * Copyright (c) 2010-2016 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 "qemu/error-report.h"
+#include "sysemu/replay.h"
+#include "replay-internal.h"
+#include "chardev/char.h"
+
+/* Char drivers that generate qemu_chr_be_write events
+ that should be saved into the log. */
+static Chardev **char_drivers;
+static int drivers_count;
+
+/* Char event attributes. */
+typedef struct CharEvent {
+ int id;
+ uint8_t *buf;
+ size_t len;
+} CharEvent;
+
+static int find_char_driver(Chardev *chr)
+{
+ int i = 0;
+ for ( ; i < drivers_count ; ++i) {
+ if (char_drivers[i] == chr) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+void replay_register_char_driver(Chardev *chr)
+{
+ if (replay_mode == REPLAY_MODE_NONE) {
+ return;
+ }
+ char_drivers = g_realloc(char_drivers,
+ sizeof(*char_drivers) * (drivers_count + 1));
+ char_drivers[drivers_count++] = chr;
+}
+
+void replay_chr_be_write(Chardev *s, uint8_t *buf, int len)
+{
+ CharEvent *event = g_malloc0(sizeof(CharEvent));
+
+ event->id = find_char_driver(s);
+ if (event->id < 0) {
+ fprintf(stderr, "Replay: cannot find char driver\n");
+ exit(1);
+ }
+ event->buf = g_malloc(len);
+ memcpy(event->buf, buf, len);
+ event->len = len;
+
+ replay_add_event(REPLAY_ASYNC_EVENT_CHAR_READ, event, NULL, 0);
+}
+
+void replay_event_char_read_run(void *opaque)
+{
+ CharEvent *event = (CharEvent *)opaque;
+
+ qemu_chr_be_write_impl(char_drivers[event->id], event->buf,
+ (int)event->len);
+
+ g_free(event->buf);
+ g_free(event);
+}
+
+void replay_event_char_read_save(void *opaque)
+{
+ CharEvent *event = (CharEvent *)opaque;
+
+ replay_put_byte(event->id);
+ replay_put_array(event->buf, event->len);
+}
+
+void *replay_event_char_read_load(void)
+{
+ CharEvent *event = g_malloc0(sizeof(CharEvent));
+
+ event->id = replay_get_byte();
+ replay_get_array_alloc(&event->buf, &event->len);
+
+ return event;
+}
+
+void replay_char_write_event_save(int res, int offset)
+{
+ g_assert(replay_mutex_locked());
+
+ replay_save_instructions();
+ replay_put_event(EVENT_CHAR_WRITE);
+ replay_put_dword(res);
+ replay_put_dword(offset);
+}
+
+void replay_char_write_event_load(int *res, int *offset)
+{
+ g_assert(replay_mutex_locked());
+
+ replay_account_executed_instructions();
+ if (replay_next_event_is(EVENT_CHAR_WRITE)) {
+ *res = replay_get_dword();
+ *offset = replay_get_dword();
+ replay_finish_event();
+ } else {
+ error_report("Missing character write event in the replay log");
+ exit(1);
+ }
+}
+
+int replay_char_read_all_load(uint8_t *buf)
+{
+ g_assert(replay_mutex_locked());
+
+ if (replay_next_event_is(EVENT_CHAR_READ_ALL)) {
+ size_t size;
+ int res;
+ replay_get_array(buf, &size);
+ replay_finish_event();
+ res = (int)size;
+ assert(res >= 0);
+ return res;
+ } else if (replay_next_event_is(EVENT_CHAR_READ_ALL_ERROR)) {
+ int res = replay_get_dword();
+ replay_finish_event();
+ return res;
+ } else {
+ error_report("Missing character read all event in the replay log");
+ exit(1);
+ }
+}
+
+void replay_char_read_all_save_error(int res)
+{
+ g_assert(replay_mutex_locked());
+ assert(res < 0);
+ replay_save_instructions();
+ replay_put_event(EVENT_CHAR_READ_ALL_ERROR);
+ replay_put_dword(res);
+}
+
+void replay_char_read_all_save_buf(uint8_t *buf, int offset)
+{
+ g_assert(replay_mutex_locked());
+ replay_save_instructions();
+ replay_put_event(EVENT_CHAR_READ_ALL);
+ replay_put_array(buf, offset);
+}
diff --git a/replay/replay-debugging.c b/replay/replay-debugging.c
new file mode 100644
index 000000000..1cde50e9f
--- /dev/null
+++ b/replay/replay-debugging.c
@@ -0,0 +1,334 @@
+/*
+ * 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. */
+ }
+ }
+}
diff --git a/replay/replay-events.c b/replay/replay-events.c
new file mode 100644
index 000000000..15983dd25
--- /dev/null
+++ b/replay/replay-events.c
@@ -0,0 +1,335 @@
+/*
+ * replay-events.c
+ *
+ * Copyright (c) 2010-2015 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 "qemu/error-report.h"
+#include "sysemu/replay.h"
+#include "replay-internal.h"
+#include "block/aio.h"
+#include "ui/input.h"
+#include "hw/core/cpu.h"
+
+typedef struct Event {
+ ReplayAsyncEventKind event_kind;
+ void *opaque;
+ void *opaque2;
+ uint64_t id;
+
+ QTAILQ_ENTRY(Event) events;
+} Event;
+
+static QTAILQ_HEAD(, Event) events_list = QTAILQ_HEAD_INITIALIZER(events_list);
+static bool events_enabled;
+
+/* Functions */
+
+static void replay_run_event(Event *event)
+{
+ switch (event->event_kind) {
+ case REPLAY_ASYNC_EVENT_BH:
+ aio_bh_call(event->opaque);
+ break;
+ case REPLAY_ASYNC_EVENT_BH_ONESHOT:
+ ((QEMUBHFunc *)event->opaque)(event->opaque2);
+ break;
+ case REPLAY_ASYNC_EVENT_INPUT:
+ qemu_input_event_send_impl(NULL, (InputEvent *)event->opaque);
+ qapi_free_InputEvent((InputEvent *)event->opaque);
+ break;
+ case REPLAY_ASYNC_EVENT_INPUT_SYNC:
+ qemu_input_event_sync_impl();
+ break;
+ case REPLAY_ASYNC_EVENT_CHAR_READ:
+ replay_event_char_read_run(event->opaque);
+ break;
+ case REPLAY_ASYNC_EVENT_BLOCK:
+ aio_bh_call(event->opaque);
+ break;
+ case REPLAY_ASYNC_EVENT_NET:
+ replay_event_net_run(event->opaque);
+ break;
+ default:
+ error_report("Replay: invalid async event ID (%d) in the queue",
+ event->event_kind);
+ exit(1);
+ break;
+ }
+}
+
+void replay_enable_events(void)
+{
+ if (replay_mode != REPLAY_MODE_NONE) {
+ events_enabled = true;
+ }
+}
+
+bool replay_has_events(void)
+{
+ return !QTAILQ_EMPTY(&events_list);
+}
+
+void replay_flush_events(void)
+{
+ if (replay_mode == REPLAY_MODE_NONE) {
+ return;
+ }
+
+ g_assert(replay_mutex_locked());
+
+ while (!QTAILQ_EMPTY(&events_list)) {
+ Event *event = QTAILQ_FIRST(&events_list);
+ replay_run_event(event);
+ QTAILQ_REMOVE(&events_list, event, events);
+ g_free(event);
+ }
+}
+
+void replay_disable_events(void)
+{
+ if (replay_mode != REPLAY_MODE_NONE) {
+ events_enabled = false;
+ /* Flush events queue before waiting of completion */
+ replay_flush_events();
+ }
+}
+
+/*! Adds specified async event to the queue */
+void replay_add_event(ReplayAsyncEventKind event_kind,
+ void *opaque,
+ void *opaque2, uint64_t id)
+{
+ assert(event_kind < REPLAY_ASYNC_COUNT);
+
+ if (!replay_file || replay_mode == REPLAY_MODE_NONE
+ || !events_enabled) {
+ Event e;
+ e.event_kind = event_kind;
+ e.opaque = opaque;
+ e.opaque2 = opaque2;
+ e.id = id;
+ replay_run_event(&e);
+ return;
+ }
+
+ Event *event = g_malloc0(sizeof(Event));
+ event->event_kind = event_kind;
+ event->opaque = opaque;
+ event->opaque2 = opaque2;
+ event->id = id;
+
+ g_assert(replay_mutex_locked());
+ QTAILQ_INSERT_TAIL(&events_list, event, events);
+ qemu_cpu_kick(first_cpu);
+}
+
+void replay_bh_schedule_event(QEMUBH *bh)
+{
+ if (events_enabled) {
+ uint64_t id = replay_get_current_icount();
+ replay_add_event(REPLAY_ASYNC_EVENT_BH, bh, NULL, id);
+ } else {
+ qemu_bh_schedule(bh);
+ }
+}
+
+void replay_bh_schedule_oneshot_event(AioContext *ctx,
+ QEMUBHFunc *cb, void *opaque)
+{
+ if (events_enabled) {
+ uint64_t id = replay_get_current_icount();
+ replay_add_event(REPLAY_ASYNC_EVENT_BH_ONESHOT, cb, opaque, id);
+ } else {
+ aio_bh_schedule_oneshot(ctx, cb, opaque);
+ }
+}
+
+void replay_add_input_event(struct InputEvent *event)
+{
+ replay_add_event(REPLAY_ASYNC_EVENT_INPUT, event, NULL, 0);
+}
+
+void replay_add_input_sync_event(void)
+{
+ replay_add_event(REPLAY_ASYNC_EVENT_INPUT_SYNC, NULL, NULL, 0);
+}
+
+void replay_block_event(QEMUBH *bh, uint64_t id)
+{
+ if (events_enabled) {
+ replay_add_event(REPLAY_ASYNC_EVENT_BLOCK, bh, NULL, id);
+ } else {
+ qemu_bh_schedule(bh);
+ }
+}
+
+static void replay_save_event(Event *event, int checkpoint)
+{
+ if (replay_mode != REPLAY_MODE_PLAY) {
+ /* put the event into the file */
+ replay_put_event(EVENT_ASYNC);
+ replay_put_byte(checkpoint);
+ replay_put_byte(event->event_kind);
+
+ /* save event-specific data */
+ switch (event->event_kind) {
+ case REPLAY_ASYNC_EVENT_BH:
+ case REPLAY_ASYNC_EVENT_BH_ONESHOT:
+ replay_put_qword(event->id);
+ break;
+ case REPLAY_ASYNC_EVENT_INPUT:
+ replay_save_input_event(event->opaque);
+ break;
+ case REPLAY_ASYNC_EVENT_INPUT_SYNC:
+ break;
+ case REPLAY_ASYNC_EVENT_CHAR_READ:
+ replay_event_char_read_save(event->opaque);
+ break;
+ case REPLAY_ASYNC_EVENT_BLOCK:
+ replay_put_qword(event->id);
+ break;
+ case REPLAY_ASYNC_EVENT_NET:
+ replay_event_net_save(event->opaque);
+ break;
+ default:
+ error_report("Unknown ID %" PRId64 " of replay event", event->id);
+ exit(1);
+ }
+ }
+}
+
+/* Called with replay mutex locked */
+void replay_save_events(int checkpoint)
+{
+ g_assert(replay_mutex_locked());
+ g_assert(checkpoint != CHECKPOINT_CLOCK_WARP_START);
+ g_assert(checkpoint != CHECKPOINT_CLOCK_VIRTUAL);
+ while (!QTAILQ_EMPTY(&events_list)) {
+ Event *event = QTAILQ_FIRST(&events_list);
+ replay_save_event(event, checkpoint);
+ replay_run_event(event);
+ QTAILQ_REMOVE(&events_list, event, events);
+ g_free(event);
+ }
+}
+
+static Event *replay_read_event(int checkpoint)
+{
+ Event *event;
+ if (replay_state.read_event_kind == -1) {
+ replay_state.read_event_checkpoint = replay_get_byte();
+ replay_state.read_event_kind = replay_get_byte();
+ replay_state.read_event_id = -1;
+ replay_check_error();
+ }
+
+ if (checkpoint != replay_state.read_event_checkpoint) {
+ return NULL;
+ }
+
+ /* Events that has not to be in the queue */
+ switch (replay_state.read_event_kind) {
+ case REPLAY_ASYNC_EVENT_BH:
+ case REPLAY_ASYNC_EVENT_BH_ONESHOT:
+ if (replay_state.read_event_id == -1) {
+ replay_state.read_event_id = replay_get_qword();
+ }
+ break;
+ case REPLAY_ASYNC_EVENT_INPUT:
+ event = g_malloc0(sizeof(Event));
+ event->event_kind = replay_state.read_event_kind;
+ event->opaque = replay_read_input_event();
+ return event;
+ case REPLAY_ASYNC_EVENT_INPUT_SYNC:
+ event = g_malloc0(sizeof(Event));
+ event->event_kind = replay_state.read_event_kind;
+ event->opaque = 0;
+ return event;
+ case REPLAY_ASYNC_EVENT_CHAR_READ:
+ event = g_malloc0(sizeof(Event));
+ event->event_kind = replay_state.read_event_kind;
+ event->opaque = replay_event_char_read_load();
+ return event;
+ case REPLAY_ASYNC_EVENT_BLOCK:
+ if (replay_state.read_event_id == -1) {
+ replay_state.read_event_id = replay_get_qword();
+ }
+ break;
+ case REPLAY_ASYNC_EVENT_NET:
+ event = g_malloc0(sizeof(Event));
+ event->event_kind = replay_state.read_event_kind;
+ event->opaque = replay_event_net_load();
+ return event;
+ default:
+ error_report("Unknown ID %d of replay event",
+ replay_state.read_event_kind);
+ exit(1);
+ break;
+ }
+
+ QTAILQ_FOREACH(event, &events_list, events) {
+ if (event->event_kind == replay_state.read_event_kind
+ && (replay_state.read_event_id == -1
+ || replay_state.read_event_id == event->id)) {
+ break;
+ }
+ }
+
+ if (event) {
+ QTAILQ_REMOVE(&events_list, event, events);
+ } else {
+ return NULL;
+ }
+
+ /* Read event-specific data */
+
+ return event;
+}
+
+/* Called with replay mutex locked */
+void replay_read_events(int checkpoint)
+{
+ g_assert(replay_mutex_locked());
+ while (replay_state.data_kind == EVENT_ASYNC) {
+ Event *event = replay_read_event(checkpoint);
+ if (!event) {
+ break;
+ }
+ replay_finish_event();
+ replay_state.read_event_kind = -1;
+ replay_run_event(event);
+
+ g_free(event);
+ }
+}
+
+void replay_init_events(void)
+{
+ replay_state.read_event_kind = -1;
+}
+
+void replay_finish_events(void)
+{
+ events_enabled = false;
+ replay_flush_events();
+}
+
+bool replay_events_enabled(void)
+{
+ return events_enabled;
+}
+
+uint64_t blkreplay_next_id(void)
+{
+ if (replay_events_enabled()) {
+ return replay_state.block_request_id++;
+ }
+ return 0;
+}
diff --git a/replay/replay-input.c b/replay/replay-input.c
new file mode 100644
index 000000000..1147e3d34
--- /dev/null
+++ b/replay/replay-input.c
@@ -0,0 +1,140 @@
+/*
+ * replay-input.c
+ *
+ * Copyright (c) 2010-2015 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 "sysemu/replay.h"
+#include "replay-internal.h"
+#include "qemu/notify.h"
+#include "ui/input.h"
+#include "qapi/clone-visitor.h"
+#include "qapi/qapi-visit-ui.h"
+
+void replay_save_input_event(InputEvent *evt)
+{
+ InputKeyEvent *key;
+ InputBtnEvent *btn;
+ InputMoveEvent *move;
+ replay_put_dword(evt->type);
+
+ switch (evt->type) {
+ case INPUT_EVENT_KIND_KEY:
+ key = evt->u.key.data;
+ replay_put_dword(key->key->type);
+
+ switch (key->key->type) {
+ case KEY_VALUE_KIND_NUMBER:
+ replay_put_qword(key->key->u.number.data);
+ replay_put_byte(key->down);
+ break;
+ case KEY_VALUE_KIND_QCODE:
+ replay_put_dword(key->key->u.qcode.data);
+ replay_put_byte(key->down);
+ break;
+ case KEY_VALUE_KIND__MAX:
+ /* keep gcc happy */
+ break;
+ }
+ break;
+ case INPUT_EVENT_KIND_BTN:
+ btn = evt->u.btn.data;
+ replay_put_dword(btn->button);
+ replay_put_byte(btn->down);
+ break;
+ case INPUT_EVENT_KIND_REL:
+ move = evt->u.rel.data;
+ replay_put_dword(move->axis);
+ replay_put_qword(move->value);
+ break;
+ case INPUT_EVENT_KIND_ABS:
+ move = evt->u.abs.data;
+ replay_put_dword(move->axis);
+ replay_put_qword(move->value);
+ break;
+ case INPUT_EVENT_KIND__MAX:
+ /* keep gcc happy */
+ break;
+ }
+}
+
+InputEvent *replay_read_input_event(void)
+{
+ InputEvent evt;
+ KeyValue keyValue;
+ InputKeyEvent key;
+ key.key = &keyValue;
+ InputBtnEvent btn;
+ InputMoveEvent rel;
+ InputMoveEvent abs;
+
+ evt.type = replay_get_dword();
+ switch (evt.type) {
+ case INPUT_EVENT_KIND_KEY:
+ evt.u.key.data = &key;
+ evt.u.key.data->key->type = replay_get_dword();
+
+ switch (evt.u.key.data->key->type) {
+ case KEY_VALUE_KIND_NUMBER:
+ evt.u.key.data->key->u.number.data = replay_get_qword();
+ evt.u.key.data->down = replay_get_byte();
+ break;
+ case KEY_VALUE_KIND_QCODE:
+ evt.u.key.data->key->u.qcode.data = (QKeyCode)replay_get_dword();
+ evt.u.key.data->down = replay_get_byte();
+ break;
+ case KEY_VALUE_KIND__MAX:
+ /* keep gcc happy */
+ break;
+ }
+ break;
+ case INPUT_EVENT_KIND_BTN:
+ evt.u.btn.data = &btn;
+ evt.u.btn.data->button = (InputButton)replay_get_dword();
+ evt.u.btn.data->down = replay_get_byte();
+ break;
+ case INPUT_EVENT_KIND_REL:
+ evt.u.rel.data = &rel;
+ evt.u.rel.data->axis = (InputAxis)replay_get_dword();
+ evt.u.rel.data->value = replay_get_qword();
+ break;
+ case INPUT_EVENT_KIND_ABS:
+ evt.u.abs.data = &abs;
+ evt.u.abs.data->axis = (InputAxis)replay_get_dword();
+ evt.u.abs.data->value = replay_get_qword();
+ break;
+ case INPUT_EVENT_KIND__MAX:
+ /* keep gcc happy */
+ break;
+ }
+
+ return QAPI_CLONE(InputEvent, &evt);
+}
+
+void replay_input_event(QemuConsole *src, InputEvent *evt)
+{
+ if (replay_mode == REPLAY_MODE_PLAY) {
+ /* Nothing */
+ } else if (replay_mode == REPLAY_MODE_RECORD) {
+ replay_add_input_event(QAPI_CLONE(InputEvent, evt));
+ } else {
+ qemu_input_event_send_impl(src, evt);
+ }
+}
+
+void replay_input_sync_event(void)
+{
+ if (replay_mode == REPLAY_MODE_PLAY) {
+ /* Nothing */
+ } else if (replay_mode == REPLAY_MODE_RECORD) {
+ replay_add_input_sync_event();
+ } else {
+ qemu_input_event_sync_impl();
+ }
+}
diff --git a/replay/replay-internal.c b/replay/replay-internal.c
new file mode 100644
index 000000000..77d0c8232
--- /dev/null
+++ b/replay/replay-internal.c
@@ -0,0 +1,285 @@
+/*
+ * replay-internal.c
+ *
+ * Copyright (c) 2010-2015 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 "sysemu/replay.h"
+#include "sysemu/runstate.h"
+#include "replay-internal.h"
+#include "qemu/error-report.h"
+#include "qemu/main-loop.h"
+
+/* Mutex to protect reading and writing events to the log.
+ data_kind and has_unread_data are also protected
+ by this mutex.
+ It also protects replay events queue which stores events to be
+ written or read to the log. */
+static QemuMutex lock;
+/* Condition and queue for fair ordering of mutex lock requests. */
+static QemuCond mutex_cond;
+static unsigned long mutex_head, mutex_tail;
+
+/* File for replay writing */
+static bool write_error;
+FILE *replay_file;
+
+static void replay_write_error(void)
+{
+ if (!write_error) {
+ error_report("replay write error");
+ write_error = true;
+ }
+}
+
+static void replay_read_error(void)
+{
+ error_report("error reading the replay data");
+ exit(1);
+}
+
+void replay_put_byte(uint8_t byte)
+{
+ if (replay_file) {
+ if (putc(byte, replay_file) == EOF) {
+ replay_write_error();
+ }
+ }
+}
+
+void replay_put_event(uint8_t event)
+{
+ assert(event < EVENT_COUNT);
+ replay_put_byte(event);
+}
+
+
+void replay_put_word(uint16_t word)
+{
+ replay_put_byte(word >> 8);
+ replay_put_byte(word);
+}
+
+void replay_put_dword(uint32_t dword)
+{
+ replay_put_word(dword >> 16);
+ replay_put_word(dword);
+}
+
+void replay_put_qword(int64_t qword)
+{
+ replay_put_dword(qword >> 32);
+ replay_put_dword(qword);
+}
+
+void replay_put_array(const uint8_t *buf, size_t size)
+{
+ if (replay_file) {
+ replay_put_dword(size);
+ if (fwrite(buf, 1, size, replay_file) != size) {
+ replay_write_error();
+ }
+ }
+}
+
+uint8_t replay_get_byte(void)
+{
+ uint8_t byte = 0;
+ if (replay_file) {
+ int r = getc(replay_file);
+ if (r == EOF) {
+ replay_read_error();
+ }
+ byte = r;
+ }
+ return byte;
+}
+
+uint16_t replay_get_word(void)
+{
+ uint16_t word = 0;
+ if (replay_file) {
+ word = replay_get_byte();
+ word = (word << 8) + replay_get_byte();
+ }
+
+ return word;
+}
+
+uint32_t replay_get_dword(void)
+{
+ uint32_t dword = 0;
+ if (replay_file) {
+ dword = replay_get_word();
+ dword = (dword << 16) + replay_get_word();
+ }
+
+ return dword;
+}
+
+int64_t replay_get_qword(void)
+{
+ int64_t qword = 0;
+ if (replay_file) {
+ qword = replay_get_dword();
+ qword = (qword << 32) + replay_get_dword();
+ }
+
+ return qword;
+}
+
+void replay_get_array(uint8_t *buf, size_t *size)
+{
+ if (replay_file) {
+ *size = replay_get_dword();
+ if (fread(buf, 1, *size, replay_file) != *size) {
+ replay_read_error();
+ }
+ }
+}
+
+void replay_get_array_alloc(uint8_t **buf, size_t *size)
+{
+ if (replay_file) {
+ *size = replay_get_dword();
+ *buf = g_malloc(*size);
+ if (fread(*buf, 1, *size, replay_file) != *size) {
+ replay_read_error();
+ }
+ }
+}
+
+void replay_check_error(void)
+{
+ if (replay_file) {
+ if (feof(replay_file)) {
+ error_report("replay file is over");
+ qemu_system_vmstop_request_prepare();
+ qemu_system_vmstop_request(RUN_STATE_PAUSED);
+ } else if (ferror(replay_file)) {
+ error_report("replay file is over or something goes wrong");
+ qemu_system_vmstop_request_prepare();
+ qemu_system_vmstop_request(RUN_STATE_INTERNAL_ERROR);
+ }
+ }
+}
+
+void replay_fetch_data_kind(void)
+{
+ if (replay_file) {
+ if (!replay_state.has_unread_data) {
+ replay_state.data_kind = replay_get_byte();
+ if (replay_state.data_kind == EVENT_INSTRUCTION) {
+ replay_state.instruction_count = replay_get_dword();
+ }
+ replay_check_error();
+ replay_state.has_unread_data = 1;
+ if (replay_state.data_kind >= EVENT_COUNT) {
+ error_report("Replay: unknown event kind %d",
+ replay_state.data_kind);
+ exit(1);
+ }
+ }
+ }
+}
+
+void replay_finish_event(void)
+{
+ replay_state.has_unread_data = 0;
+ replay_fetch_data_kind();
+}
+
+static __thread bool replay_locked;
+
+void replay_mutex_init(void)
+{
+ qemu_mutex_init(&lock);
+ qemu_cond_init(&mutex_cond);
+ /* Hold the mutex while we start-up */
+ replay_locked = true;
+ ++mutex_tail;
+}
+
+bool replay_mutex_locked(void)
+{
+ return replay_locked;
+}
+
+/* Ordering constraints, replay_lock must be taken before BQL */
+void replay_mutex_lock(void)
+{
+ if (replay_mode != REPLAY_MODE_NONE) {
+ unsigned long id;
+ g_assert(!qemu_mutex_iothread_locked());
+ g_assert(!replay_mutex_locked());
+ qemu_mutex_lock(&lock);
+ id = mutex_tail++;
+ while (id != mutex_head) {
+ qemu_cond_wait(&mutex_cond, &lock);
+ }
+ replay_locked = true;
+ qemu_mutex_unlock(&lock);
+ }
+}
+
+void replay_mutex_unlock(void)
+{
+ if (replay_mode != REPLAY_MODE_NONE) {
+ g_assert(replay_mutex_locked());
+ qemu_mutex_lock(&lock);
+ ++mutex_head;
+ replay_locked = false;
+ qemu_cond_broadcast(&mutex_cond);
+ qemu_mutex_unlock(&lock);
+ }
+}
+
+void replay_advance_current_icount(uint64_t current_icount)
+{
+ int diff = (int)(current_icount - replay_state.current_icount);
+
+ /* Time can only go forward */
+ assert(diff >= 0);
+
+ if (replay_mode == REPLAY_MODE_RECORD) {
+ if (diff > 0) {
+ replay_put_event(EVENT_INSTRUCTION);
+ replay_put_dword(diff);
+ replay_state.current_icount += diff;
+ }
+ } else if (replay_mode == REPLAY_MODE_PLAY) {
+ if (diff > 0) {
+ replay_state.instruction_count -= diff;
+ replay_state.current_icount += diff;
+ if (replay_state.instruction_count == 0) {
+ assert(replay_state.data_kind == EVENT_INSTRUCTION);
+ replay_finish_event();
+ /* Wake up iothread. This is required because
+ timers will not expire until clock counters
+ will be read from the log. */
+ qemu_notify_event();
+ }
+ }
+ /* Execution reached the break step */
+ if (replay_break_icount == replay_state.current_icount) {
+ /* Cannot make callback directly from the vCPU thread */
+ timer_mod_ns(replay_break_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_REALTIME));
+ }
+ }
+}
+
+/*! Saves cached instructions. */
+void replay_save_instructions(void)
+{
+ if (replay_file && replay_mode == REPLAY_MODE_RECORD) {
+ g_assert(replay_mutex_locked());
+ replay_advance_current_icount(replay_get_current_icount());
+ }
+}
diff --git a/replay/replay-internal.h b/replay/replay-internal.h
new file mode 100644
index 000000000..97649ed8d
--- /dev/null
+++ b/replay/replay-internal.h
@@ -0,0 +1,198 @@
+#ifndef REPLAY_INTERNAL_H
+#define REPLAY_INTERNAL_H
+
+/*
+ * replay-internal.h
+ *
+ * Copyright (c) 2010-2015 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.
+ *
+ */
+
+/* Any changes to order/number of events will need to bump REPLAY_VERSION */
+enum ReplayEvents {
+ /* for instruction event */
+ EVENT_INSTRUCTION,
+ /* for software interrupt */
+ EVENT_INTERRUPT,
+ /* for emulated exceptions */
+ EVENT_EXCEPTION,
+ /* for async events */
+ EVENT_ASYNC,
+ /* for shutdown requests, range allows recovery of ShutdownCause */
+ EVENT_SHUTDOWN,
+ EVENT_SHUTDOWN_LAST = EVENT_SHUTDOWN + SHUTDOWN_CAUSE__MAX,
+ /* for character device write event */
+ EVENT_CHAR_WRITE,
+ /* for character device read all event */
+ EVENT_CHAR_READ_ALL,
+ EVENT_CHAR_READ_ALL_ERROR,
+ /* for audio out event */
+ EVENT_AUDIO_OUT,
+ /* for audio in event */
+ EVENT_AUDIO_IN,
+ /* for random number generator */
+ EVENT_RANDOM,
+ /* for clock read/writes */
+ /* some of greater codes are reserved for clocks */
+ EVENT_CLOCK,
+ EVENT_CLOCK_LAST = EVENT_CLOCK + REPLAY_CLOCK_COUNT - 1,
+ /* for checkpoint event */
+ /* some of greater codes are reserved for checkpoints */
+ EVENT_CHECKPOINT,
+ EVENT_CHECKPOINT_LAST = EVENT_CHECKPOINT + CHECKPOINT_COUNT - 1,
+ /* end of log event */
+ EVENT_END,
+ EVENT_COUNT
+};
+
+/* Asynchronous events IDs */
+
+enum ReplayAsyncEventKind {
+ REPLAY_ASYNC_EVENT_BH,
+ REPLAY_ASYNC_EVENT_BH_ONESHOT,
+ REPLAY_ASYNC_EVENT_INPUT,
+ REPLAY_ASYNC_EVENT_INPUT_SYNC,
+ REPLAY_ASYNC_EVENT_CHAR_READ,
+ REPLAY_ASYNC_EVENT_BLOCK,
+ REPLAY_ASYNC_EVENT_NET,
+ REPLAY_ASYNC_COUNT
+};
+
+typedef enum ReplayAsyncEventKind ReplayAsyncEventKind;
+
+typedef struct ReplayState {
+ /*! Cached clock values. */
+ int64_t cached_clock[REPLAY_CLOCK_COUNT];
+ /*! Current icount - number of processed instructions. */
+ uint64_t current_icount;
+ /*! Number of instructions to be executed before other events happen. */
+ int instruction_count;
+ /*! Type of the currently executed event. */
+ unsigned int data_kind;
+ /*! Flag which indicates that event is not processed yet. */
+ unsigned int has_unread_data;
+ /*! Temporary variable for saving current log offset. */
+ uint64_t file_offset;
+ /*! Next block operation id.
+ This counter is global, because requests from different
+ block devices should not get overlapping ids. */
+ uint64_t block_request_id;
+ /*! Prior value of the host clock */
+ uint64_t host_clock_last;
+ /*! Asynchronous event type read from the log */
+ int32_t read_event_kind;
+ /*! Asynchronous event id read from the log */
+ uint64_t read_event_id;
+ /*! Asynchronous event checkpoint id read from the log */
+ int32_t read_event_checkpoint;
+} ReplayState;
+extern ReplayState replay_state;
+
+/* File for replay writing */
+extern FILE *replay_file;
+/* Instruction count of the replay breakpoint */
+extern uint64_t replay_break_icount;
+/* Timer for the replay breakpoint callback */
+extern QEMUTimer *replay_break_timer;
+
+void replay_put_byte(uint8_t byte);
+void replay_put_event(uint8_t event);
+void replay_put_word(uint16_t word);
+void replay_put_dword(uint32_t dword);
+void replay_put_qword(int64_t qword);
+void replay_put_array(const uint8_t *buf, size_t size);
+
+uint8_t replay_get_byte(void);
+uint16_t replay_get_word(void);
+uint32_t replay_get_dword(void);
+int64_t replay_get_qword(void);
+void replay_get_array(uint8_t *buf, size_t *size);
+void replay_get_array_alloc(uint8_t **buf, size_t *size);
+
+/* Mutex functions for protecting replay log file and ensuring
+ * synchronisation between vCPU and main-loop threads. */
+
+void replay_mutex_init(void);
+bool replay_mutex_locked(void);
+
+/*! Checks error status of the file. */
+void replay_check_error(void);
+
+/*! Finishes processing of the replayed event and fetches
+ the next event from the log. */
+void replay_finish_event(void);
+/*! Reads data type from the file and stores it in the
+ data_kind variable. */
+void replay_fetch_data_kind(void);
+
+/*! Advance replay_state.current_icount to the specified value. */
+void replay_advance_current_icount(uint64_t current_icount);
+/*! Saves queued events (like instructions and sound). */
+void replay_save_instructions(void);
+
+/*! Skips async events until some sync event will be found.
+ \return true, if event was found */
+bool replay_next_event_is(int event);
+
+/*! Reads next clock value from the file.
+ If clock kind read from the file is different from the parameter,
+ the value is not used. */
+void replay_read_next_clock(unsigned int kind);
+
+/* Asynchronous events queue */
+
+/*! Initializes events' processing internals */
+void replay_init_events(void);
+/*! Clears internal data structures for events handling */
+void replay_finish_events(void);
+/*! Returns true if there are any unsaved events in the queue */
+bool replay_has_events(void);
+/*! Saves events from queue into the file */
+void replay_save_events(int checkpoint);
+/*! Read events from the file into the input queue */
+void replay_read_events(int checkpoint);
+/*! Adds specified async event to the queue */
+void replay_add_event(ReplayAsyncEventKind event_kind, void *opaque,
+ void *opaque2, uint64_t id);
+
+/* Input events */
+
+/*! Saves input event to the log */
+void replay_save_input_event(InputEvent *evt);
+/*! Reads input event from the log */
+InputEvent *replay_read_input_event(void);
+/*! Adds input event to the queue */
+void replay_add_input_event(struct InputEvent *event);
+/*! Adds input sync event to the queue */
+void replay_add_input_sync_event(void);
+
+/* Character devices */
+
+/*! Called to run char device read event. */
+void replay_event_char_read_run(void *opaque);
+/*! Writes char read event to the file. */
+void replay_event_char_read_save(void *opaque);
+/*! Reads char event read from the file. */
+void *replay_event_char_read_load(void);
+
+/* Network devices */
+
+/*! Called to run network event. */
+void replay_event_net_run(void *opaque);
+/*! Writes network event to the file. */
+void replay_event_net_save(void *opaque);
+/*! Reads network from the file. */
+void *replay_event_net_load(void);
+
+/* VMState-related functions */
+
+/* Registers replay VMState.
+ Should be called before virtual devices initialization
+ to make cached timers available for post_load functions. */
+void replay_vmstate_register(void);
+
+#endif
diff --git a/replay/replay-net.c b/replay/replay-net.c
new file mode 100644
index 000000000..3b70f71cf
--- /dev/null
+++ b/replay/replay-net.c
@@ -0,0 +1,101 @@
+/*
+ * replay-net.c
+ *
+ * Copyright (c) 2010-2016 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 "qemu/error-report.h"
+#include "sysemu/replay.h"
+#include "replay-internal.h"
+#include "net/net.h"
+#include "net/filter.h"
+#include "qemu/iov.h"
+
+struct ReplayNetState {
+ NetFilterState *nfs;
+ int id;
+};
+
+typedef struct NetEvent {
+ uint8_t id;
+ uint32_t flags;
+ uint8_t *data;
+ size_t size;
+} NetEvent;
+
+static NetFilterState **network_filters;
+static int network_filters_count;
+
+ReplayNetState *replay_register_net(NetFilterState *nfs)
+{
+ ReplayNetState *rns = g_new0(ReplayNetState, 1);
+ rns->nfs = nfs;
+ rns->id = network_filters_count++;
+ network_filters = g_realloc(network_filters,
+ network_filters_count
+ * sizeof(*network_filters));
+ network_filters[network_filters_count - 1] = nfs;
+ return rns;
+}
+
+void replay_unregister_net(ReplayNetState *rns)
+{
+ network_filters[rns->id] = NULL;
+ g_free(rns);
+}
+
+void replay_net_packet_event(ReplayNetState *rns, unsigned flags,
+ const struct iovec *iov, int iovcnt)
+{
+ NetEvent *event = g_new(NetEvent, 1);
+ event->flags = flags;
+ event->data = g_malloc(iov_size(iov, iovcnt));
+ event->size = iov_size(iov, iovcnt);
+ event->id = rns->id;
+ iov_to_buf(iov, iovcnt, 0, event->data, event->size);
+
+ replay_add_event(REPLAY_ASYNC_EVENT_NET, event, NULL, 0);
+}
+
+void replay_event_net_run(void *opaque)
+{
+ NetEvent *event = opaque;
+ struct iovec iov = {
+ .iov_base = (void *)event->data,
+ .iov_len = event->size
+ };
+
+ assert(event->id < network_filters_count);
+
+ qemu_netfilter_pass_to_next(network_filters[event->id]->netdev,
+ event->flags, &iov, 1, network_filters[event->id]);
+
+ g_free(event->data);
+ g_free(event);
+}
+
+void replay_event_net_save(void *opaque)
+{
+ NetEvent *event = opaque;
+
+ replay_put_byte(event->id);
+ replay_put_dword(event->flags);
+ replay_put_array(event->data, event->size);
+}
+
+void *replay_event_net_load(void)
+{
+ NetEvent *event = g_new(NetEvent, 1);
+
+ event->id = replay_get_byte();
+ event->flags = replay_get_dword();
+ replay_get_array_alloc(&event->data, &event->size);
+
+ return event;
+}
diff --git a/replay/replay-random.c b/replay/replay-random.c
new file mode 100644
index 000000000..afc7a0fcc
--- /dev/null
+++ b/replay/replay-random.c
@@ -0,0 +1,44 @@
+/*
+ * replay-random.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 "qemu/error-report.h"
+#include "sysemu/replay.h"
+#include "replay-internal.h"
+
+void replay_save_random(int ret, void *buf, size_t len)
+{
+ g_assert(replay_mutex_locked());
+
+ replay_save_instructions();
+ replay_put_event(EVENT_RANDOM);
+ replay_put_dword(ret);
+ replay_put_array(buf, len);
+}
+
+int replay_read_random(void *buf, size_t len)
+{
+ int ret = 0;
+ g_assert(replay_mutex_locked());
+
+ replay_account_executed_instructions();
+ if (replay_next_event_is(EVENT_RANDOM)) {
+ size_t buf_size = 0;
+ ret = replay_get_dword();
+ replay_get_array(buf, &buf_size);
+ replay_finish_event();
+ g_assert(buf_size == len);
+ } else {
+ error_report("Missing random event in the replay log");
+ exit(1);
+ }
+ return ret;
+}
diff --git a/replay/replay-snapshot.c b/replay/replay-snapshot.c
new file mode 100644
index 000000000..e8767a193
--- /dev/null
+++ b/replay/replay-snapshot.c
@@ -0,0 +1,100 @@
+/*
+ * replay-snapshot.c
+ *
+ * Copyright (c) 2010-2016 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 "replay-internal.h"
+#include "monitor/monitor.h"
+#include "qapi/qmp/qstring.h"
+#include "qemu/error-report.h"
+#include "migration/vmstate.h"
+#include "migration/snapshot.h"
+
+static int replay_pre_save(void *opaque)
+{
+ ReplayState *state = opaque;
+ state->file_offset = ftell(replay_file);
+
+ return 0;
+}
+
+static int replay_post_load(void *opaque, int version_id)
+{
+ ReplayState *state = opaque;
+ if (replay_mode == REPLAY_MODE_PLAY) {
+ fseek(replay_file, state->file_offset, SEEK_SET);
+ /* If this was a vmstate, saved in recording mode,
+ we need to initialize replay data fields. */
+ replay_fetch_data_kind();
+ } else if (replay_mode == REPLAY_MODE_RECORD) {
+ /* This is only useful for loading the initial state.
+ Therefore reset all the counters. */
+ state->instruction_count = 0;
+ state->block_request_id = 0;
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_replay = {
+ .name = "replay",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .pre_save = replay_pre_save,
+ .post_load = replay_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT64_ARRAY(cached_clock, ReplayState, REPLAY_CLOCK_COUNT),
+ VMSTATE_UINT64(current_icount, ReplayState),
+ VMSTATE_INT32(instruction_count, ReplayState),
+ VMSTATE_UINT32(data_kind, ReplayState),
+ VMSTATE_UINT32(has_unread_data, ReplayState),
+ VMSTATE_UINT64(file_offset, ReplayState),
+ VMSTATE_UINT64(block_request_id, ReplayState),
+ VMSTATE_INT32(read_event_kind, ReplayState),
+ VMSTATE_UINT64(read_event_id, ReplayState),
+ VMSTATE_INT32(read_event_checkpoint, ReplayState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+void replay_vmstate_register(void)
+{
+ vmstate_register(NULL, 0, &vmstate_replay, &replay_state);
+}
+
+void replay_vmstate_init(void)
+{
+ Error *err = NULL;
+
+ if (replay_snapshot) {
+ if (replay_mode == REPLAY_MODE_RECORD) {
+ if (!save_snapshot(replay_snapshot,
+ true, NULL, false, NULL, &err)) {
+ error_report_err(err);
+ error_report("Could not create snapshot for icount record");
+ exit(1);
+ }
+ } else if (replay_mode == REPLAY_MODE_PLAY) {
+ if (!load_snapshot(replay_snapshot, NULL, false, NULL, &err)) {
+ error_report_err(err);
+ error_report("Could not load snapshot for icount replay");
+ exit(1);
+ }
+ }
+ }
+}
+
+bool replay_can_snapshot(void)
+{
+ return replay_mode == REPLAY_MODE_NONE
+ || !replay_has_events();
+}
diff --git a/replay/replay-time.c b/replay/replay-time.c
new file mode 100644
index 000000000..00ebcb7a4
--- /dev/null
+++ b/replay/replay-time.c
@@ -0,0 +1,62 @@
+/*
+ * replay-time.c
+ *
+ * Copyright (c) 2010-2015 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 "sysemu/replay.h"
+#include "replay-internal.h"
+#include "qemu/error-report.h"
+
+int64_t replay_save_clock(ReplayClockKind kind, int64_t clock,
+ int64_t raw_icount)
+{
+ g_assert(replay_file);
+ g_assert(replay_mutex_locked());
+
+ /*
+ * Due to the caller's locking requirements we get the icount from it
+ * instead of using replay_save_instructions().
+ */
+ replay_advance_current_icount(raw_icount);
+ replay_put_event(EVENT_CLOCK + kind);
+ replay_put_qword(clock);
+
+ return clock;
+}
+
+void replay_read_next_clock(ReplayClockKind kind)
+{
+ unsigned int read_kind = replay_state.data_kind - EVENT_CLOCK;
+
+ assert(read_kind == kind);
+
+ int64_t clock = replay_get_qword();
+
+ replay_check_error();
+ replay_finish_event();
+
+ replay_state.cached_clock[read_kind] = clock;
+}
+
+/*! Reads next clock event from the input. */
+int64_t replay_read_clock(ReplayClockKind kind, int64_t raw_icount)
+{
+ int64_t ret;
+ g_assert(replay_file && replay_mutex_locked());
+
+ replay_advance_current_icount(raw_icount);
+
+ if (replay_next_event_is(EVENT_CLOCK + kind)) {
+ replay_read_next_clock(kind);
+ }
+ ret = replay_state.cached_clock[kind];
+
+ return ret;
+}
diff --git a/replay/replay.c b/replay/replay.c
new file mode 100644
index 000000000..6df2abc18
--- /dev/null
+++ b/replay/replay.c
@@ -0,0 +1,403 @@
+/*
+ * replay.c
+ *
+ * Copyright (c) 2010-2015 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/cpu-timers.h"
+#include "sysemu/replay.h"
+#include "sysemu/runstate.h"
+#include "replay-internal.h"
+#include "qemu/main-loop.h"
+#include "qemu/option.h"
+#include "sysemu/cpus.h"
+#include "qemu/error-report.h"
+
+/* Current version of the replay mechanism.
+ Increase it when file format changes. */
+#define REPLAY_VERSION 0xe0200a
+/* Size of replay log header */
+#define HEADER_SIZE (sizeof(uint32_t) + sizeof(uint64_t))
+
+ReplayMode replay_mode = REPLAY_MODE_NONE;
+char *replay_snapshot;
+
+/* Name of replay file */
+static char *replay_filename;
+ReplayState replay_state;
+static GSList *replay_blockers;
+
+/* Replay breakpoints */
+uint64_t replay_break_icount = -1ULL;
+QEMUTimer *replay_break_timer;
+
+bool replay_next_event_is(int event)
+{
+ bool res = false;
+
+ /* nothing to skip - not all instructions used */
+ if (replay_state.instruction_count != 0) {
+ assert(replay_state.data_kind == EVENT_INSTRUCTION);
+ return event == EVENT_INSTRUCTION;
+ }
+
+ while (true) {
+ unsigned int data_kind = replay_state.data_kind;
+ if (event == data_kind) {
+ res = true;
+ }
+ switch (data_kind) {
+ case EVENT_SHUTDOWN ... EVENT_SHUTDOWN_LAST:
+ replay_finish_event();
+ qemu_system_shutdown_request(data_kind - EVENT_SHUTDOWN);
+ break;
+ default:
+ /* clock, time_t, checkpoint and other events */
+ return res;
+ }
+ }
+ return res;
+}
+
+uint64_t replay_get_current_icount(void)
+{
+ return icount_get_raw();
+}
+
+int replay_get_instructions(void)
+{
+ int res = 0;
+ replay_mutex_lock();
+ if (replay_next_event_is(EVENT_INSTRUCTION)) {
+ res = replay_state.instruction_count;
+ if (replay_break_icount != -1LL) {
+ uint64_t current = replay_get_current_icount();
+ assert(replay_break_icount >= current);
+ if (current + res > replay_break_icount) {
+ res = replay_break_icount - current;
+ }
+ }
+ }
+ replay_mutex_unlock();
+ return res;
+}
+
+void replay_account_executed_instructions(void)
+{
+ if (replay_mode == REPLAY_MODE_PLAY) {
+ g_assert(replay_mutex_locked());
+ if (replay_state.instruction_count > 0) {
+ replay_advance_current_icount(replay_get_current_icount());
+ }
+ }
+}
+
+bool replay_exception(void)
+{
+
+ if (replay_mode == REPLAY_MODE_RECORD) {
+ g_assert(replay_mutex_locked());
+ replay_save_instructions();
+ replay_put_event(EVENT_EXCEPTION);
+ return true;
+ } else if (replay_mode == REPLAY_MODE_PLAY) {
+ g_assert(replay_mutex_locked());
+ bool res = replay_has_exception();
+ if (res) {
+ replay_finish_event();
+ }
+ return res;
+ }
+
+ return true;
+}
+
+bool replay_has_exception(void)
+{
+ bool res = false;
+ if (replay_mode == REPLAY_MODE_PLAY) {
+ g_assert(replay_mutex_locked());
+ replay_account_executed_instructions();
+ res = replay_next_event_is(EVENT_EXCEPTION);
+ }
+
+ return res;
+}
+
+bool replay_interrupt(void)
+{
+ if (replay_mode == REPLAY_MODE_RECORD) {
+ g_assert(replay_mutex_locked());
+ replay_save_instructions();
+ replay_put_event(EVENT_INTERRUPT);
+ return true;
+ } else if (replay_mode == REPLAY_MODE_PLAY) {
+ g_assert(replay_mutex_locked());
+ bool res = replay_has_interrupt();
+ if (res) {
+ replay_finish_event();
+ }
+ return res;
+ }
+
+ return true;
+}
+
+bool replay_has_interrupt(void)
+{
+ bool res = false;
+ if (replay_mode == REPLAY_MODE_PLAY) {
+ g_assert(replay_mutex_locked());
+ replay_account_executed_instructions();
+ res = replay_next_event_is(EVENT_INTERRUPT);
+ }
+ return res;
+}
+
+void replay_shutdown_request(ShutdownCause cause)
+{
+ if (replay_mode == REPLAY_MODE_RECORD) {
+ g_assert(replay_mutex_locked());
+ replay_put_event(EVENT_SHUTDOWN + cause);
+ }
+}
+
+bool replay_checkpoint(ReplayCheckpoint checkpoint)
+{
+ bool res = false;
+ static bool in_checkpoint;
+ assert(EVENT_CHECKPOINT + checkpoint <= EVENT_CHECKPOINT_LAST);
+
+ if (!replay_file) {
+ return true;
+ }
+
+ if (in_checkpoint) {
+ /*
+ Recursion occurs when HW event modifies timers.
+ Prevent performing icount warp in this case and
+ wait for another invocation of the checkpoint.
+ */
+ g_assert(replay_mode == REPLAY_MODE_PLAY);
+ return false;
+ }
+ in_checkpoint = true;
+
+ replay_save_instructions();
+
+ if (replay_mode == REPLAY_MODE_PLAY) {
+ g_assert(replay_mutex_locked());
+ if (replay_next_event_is(EVENT_CHECKPOINT + checkpoint)) {
+ replay_finish_event();
+ } else if (replay_state.data_kind != EVENT_ASYNC) {
+ res = false;
+ goto out;
+ }
+ replay_read_events(checkpoint);
+ /* replay_read_events may leave some unread events.
+ Return false if not all of the events associated with
+ checkpoint were processed */
+ res = replay_state.data_kind != EVENT_ASYNC;
+ } else if (replay_mode == REPLAY_MODE_RECORD) {
+ g_assert(replay_mutex_locked());
+ replay_put_event(EVENT_CHECKPOINT + checkpoint);
+ /* This checkpoint belongs to several threads.
+ Processing events from different threads is
+ non-deterministic */
+ if (checkpoint != CHECKPOINT_CLOCK_WARP_START
+ /* FIXME: this is temporary fix, other checkpoints
+ may also be invoked from the different threads someday.
+ Asynchronous event processing should be refactored
+ to create additional replay event kind which is
+ nailed to the one of the threads and which processes
+ the event queue. */
+ && checkpoint != CHECKPOINT_CLOCK_VIRTUAL) {
+ replay_save_events(checkpoint);
+ }
+ res = true;
+ }
+out:
+ in_checkpoint = false;
+ return res;
+}
+
+bool replay_has_checkpoint(void)
+{
+ bool res = false;
+ if (replay_mode == REPLAY_MODE_PLAY) {
+ g_assert(replay_mutex_locked());
+ replay_account_executed_instructions();
+ res = EVENT_CHECKPOINT <= replay_state.data_kind
+ && replay_state.data_kind <= EVENT_CHECKPOINT_LAST;
+ }
+ return res;
+}
+
+static void replay_enable(const char *fname, int mode)
+{
+ const char *fmode = NULL;
+ assert(!replay_file);
+
+ switch (mode) {
+ case REPLAY_MODE_RECORD:
+ fmode = "wb";
+ break;
+ case REPLAY_MODE_PLAY:
+ fmode = "rb";
+ break;
+ default:
+ fprintf(stderr, "Replay: internal error: invalid replay mode\n");
+ exit(1);
+ }
+
+ atexit(replay_finish);
+
+ replay_file = fopen(fname, fmode);
+ if (replay_file == NULL) {
+ fprintf(stderr, "Replay: open %s: %s\n", fname, strerror(errno));
+ exit(1);
+ }
+
+ replay_filename = g_strdup(fname);
+ replay_mode = mode;
+ replay_mutex_init();
+
+ replay_state.data_kind = -1;
+ replay_state.instruction_count = 0;
+ replay_state.current_icount = 0;
+ replay_state.has_unread_data = 0;
+
+ /* skip file header for RECORD and check it for PLAY */
+ if (replay_mode == REPLAY_MODE_RECORD) {
+ fseek(replay_file, HEADER_SIZE, SEEK_SET);
+ } else if (replay_mode == REPLAY_MODE_PLAY) {
+ unsigned int version = replay_get_dword();
+ if (version != REPLAY_VERSION) {
+ fprintf(stderr, "Replay: invalid input log file version\n");
+ exit(1);
+ }
+ /* go to the beginning */
+ fseek(replay_file, HEADER_SIZE, SEEK_SET);
+ replay_fetch_data_kind();
+ }
+
+ replay_init_events();
+}
+
+void replay_configure(QemuOpts *opts)
+{
+ const char *fname;
+ const char *rr;
+ ReplayMode mode = REPLAY_MODE_NONE;
+ Location loc;
+
+ if (!opts) {
+ return;
+ }
+
+ loc_push_none(&loc);
+ qemu_opts_loc_restore(opts);
+
+ rr = qemu_opt_get(opts, "rr");
+ if (!rr) {
+ /* Just enabling icount */
+ goto out;
+ } else if (!strcmp(rr, "record")) {
+ mode = REPLAY_MODE_RECORD;
+ } else if (!strcmp(rr, "replay")) {
+ mode = REPLAY_MODE_PLAY;
+ } else {
+ error_report("Invalid icount rr option: %s", rr);
+ exit(1);
+ }
+
+ fname = qemu_opt_get(opts, "rrfile");
+ if (!fname) {
+ error_report("File name not specified for replay");
+ exit(1);
+ }
+
+ replay_snapshot = g_strdup(qemu_opt_get(opts, "rrsnapshot"));
+ replay_vmstate_register();
+ replay_enable(fname, mode);
+
+out:
+ loc_pop(&loc);
+}
+
+void replay_start(void)
+{
+ if (replay_mode == REPLAY_MODE_NONE) {
+ return;
+ }
+
+ if (replay_blockers) {
+ error_reportf_err(replay_blockers->data, "Record/replay: ");
+ exit(1);
+ }
+ if (!icount_enabled()) {
+ error_report("Please enable icount to use record/replay");
+ exit(1);
+ }
+
+ /* Timer for snapshotting will be set up here. */
+
+ replay_enable_events();
+}
+
+void replay_finish(void)
+{
+ if (replay_mode == REPLAY_MODE_NONE) {
+ return;
+ }
+
+ replay_save_instructions();
+
+ /* finalize the file */
+ if (replay_file) {
+ if (replay_mode == REPLAY_MODE_RECORD) {
+ /*
+ * Can't do it in the signal handler, therefore
+ * add shutdown event here for the case of Ctrl-C.
+ */
+ replay_shutdown_request(SHUTDOWN_CAUSE_HOST_SIGNAL);
+ /* write end event */
+ replay_put_event(EVENT_END);
+
+ /* write header */
+ fseek(replay_file, 0, SEEK_SET);
+ replay_put_dword(REPLAY_VERSION);
+ }
+
+ fclose(replay_file);
+ replay_file = NULL;
+ }
+ if (replay_filename) {
+ g_free(replay_filename);
+ replay_filename = NULL;
+ }
+
+ g_free(replay_snapshot);
+ replay_snapshot = NULL;
+
+ replay_mode = REPLAY_MODE_NONE;
+
+ replay_finish_events();
+}
+
+void replay_add_blocker(Error *reason)
+{
+ replay_blockers = g_slist_prepend(replay_blockers, reason);
+}
+
+const char *replay_get_filename(void)
+{
+ return replay_filename;
+}
diff --git a/replay/stubs-system.c b/replay/stubs-system.c
new file mode 100644
index 000000000..5c262b08f
--- /dev/null
+++ b/replay/stubs-system.c
@@ -0,0 +1,96 @@
+#include "qemu/osdep.h"
+#include "sysemu/replay.h"
+#include "ui/input.h"
+
+void replay_input_event(QemuConsole *src, InputEvent *evt)
+{
+ qemu_input_event_send_impl(src, evt);
+}
+
+void replay_input_sync_event(void)
+{
+ qemu_input_event_sync_impl();
+}
+
+void replay_add_blocker(Error *reason)
+{
+}
+void replay_audio_in(size_t *recorded, void *samples, size_t *wpos, size_t size)
+{
+}
+void replay_audio_out(size_t *played)
+{
+}
+void replay_breakpoint(void)
+{
+}
+bool replay_can_snapshot(void)
+{
+ return true;
+}
+void replay_configure(struct QemuOpts *opts)
+{
+}
+void replay_flush_events(void)
+{
+}
+void replay_gdb_attached(void)
+{
+}
+bool replay_running_debug(void)
+{
+ return false;
+}
+void replay_shutdown_request(ShutdownCause cause)
+{
+}
+void replay_start(void)
+{
+}
+void replay_vmstate_init(void)
+{
+}
+
+#include "monitor/monitor.h"
+#include "monitor/hmp.h"
+#include "qapi/qapi-commands-replay.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+
+void hmp_info_replay(Monitor *mon, const QDict *qdict)
+{
+ error_report("replay support not available");
+}
+void hmp_replay_break(Monitor *mon, const QDict *qdict)
+{
+ error_report("replay support not available");
+}
+void hmp_replay_delete_break(Monitor *mon, const QDict *qdict)
+{
+ error_report("replay support not available");
+}
+void hmp_replay_seek(Monitor *mon, const QDict *qdict)
+{
+ error_report("replay support not available");
+}
+ReplayInfo *qmp_query_replay(Error **errp)
+{
+ error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND,
+ "replay support not available");
+ return NULL;
+}
+void qmp_replay_break(int64_t icount, Error **errp)
+{
+ error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND,
+ "replay support not available");
+}
+void qmp_replay_delete_break(Error **errp)
+{
+ error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND,
+ "replay support not available");
+}
+void qmp_replay_seek(int64_t icount, Error **errp)
+{
+ error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND,
+ "replay support not available");
+}