diff options
author | Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com> | 2023-10-10 11:40:56 +0000 |
---|---|---|
committer | Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com> | 2023-10-10 11:40:56 +0000 |
commit | e02cda008591317b1625707ff8e115a4841aa889 (patch) | |
tree | aee302e3cf8b59ec2d32ec481be3d1afddfc8968 /replay | |
parent | cc668e6b7e0ffd8c9d130513d12053cf5eda1d3b (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.build | 13 | ||||
-rw-r--r-- | replay/replay-audio.c | 72 | ||||
-rw-r--r-- | replay/replay-char.c | 158 | ||||
-rw-r--r-- | replay/replay-debugging.c | 334 | ||||
-rw-r--r-- | replay/replay-events.c | 335 | ||||
-rw-r--r-- | replay/replay-input.c | 140 | ||||
-rw-r--r-- | replay/replay-internal.c | 285 | ||||
-rw-r--r-- | replay/replay-internal.h | 198 | ||||
-rw-r--r-- | replay/replay-net.c | 101 | ||||
-rw-r--r-- | replay/replay-random.c | 44 | ||||
-rw-r--r-- | replay/replay-snapshot.c | 100 | ||||
-rw-r--r-- | replay/replay-time.c | 62 | ||||
-rw-r--r-- | replay/replay.c | 403 | ||||
-rw-r--r-- | replay/stubs-system.c | 96 |
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"); +} |