Import QEMU upstream snapshot d2e570c
Upstream: https://gitlab.com/qemu-project/qemu.git Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
system_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'))
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 "system/replay.h"
|
||||
#include "replay-internal.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_start(size_t *nsamples)
|
||||
{
|
||||
if (replay_mode == REPLAY_MODE_RECORD) {
|
||||
g_assert(replay_mutex_locked());
|
||||
replay_save_instructions();
|
||||
replay_put_event(EVENT_AUDIO_IN);
|
||||
replay_put_qword(*nsamples);
|
||||
replay_state.n_audio_samples = *nsamples;
|
||||
} else if (replay_mode == REPLAY_MODE_PLAY) {
|
||||
g_assert(replay_mutex_locked());
|
||||
replay_account_executed_instructions();
|
||||
if (replay_next_event_is(EVENT_AUDIO_IN)) {
|
||||
*nsamples = replay_get_qword();
|
||||
replay_state.n_audio_samples = *nsamples;
|
||||
} else {
|
||||
error_report("Missing audio in event in the replay log");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void replay_audio_in_sample_lr(uint64_t *left, uint64_t *right)
|
||||
{
|
||||
if (replay_mode == REPLAY_MODE_RECORD) {
|
||||
replay_put_qword(*left);
|
||||
replay_put_qword(*right);
|
||||
} else if (replay_mode == REPLAY_MODE_PLAY) {
|
||||
*left = replay_get_qword();
|
||||
*right = replay_get_qword();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(replay_state.n_audio_samples > 0);
|
||||
replay_state.n_audio_samples--;
|
||||
}
|
||||
|
||||
void replay_audio_in_finish(void)
|
||||
{
|
||||
assert(replay_state.n_audio_samples == 0);
|
||||
|
||||
if (replay_mode == REPLAY_MODE_PLAY) {
|
||||
replay_finish_event();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* 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 "system/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, const uint8_t *buf, int len)
|
||||
{
|
||||
CharEvent *event = g_new0(CharEvent, 1);
|
||||
|
||||
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_new0(CharEvent, 1);
|
||||
|
||||
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 {
|
||||
replay_sync_error("Missing character write event in the replay log");
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
assert(size <= INT_MAX);
|
||||
res = (int)size;
|
||||
return res;
|
||||
} else if (replay_next_event_is(EVENT_CHAR_READ_ALL_ERROR)) {
|
||||
int res = replay_get_dword();
|
||||
replay_finish_event();
|
||||
return res;
|
||||
} else {
|
||||
replay_sync_error("Missing character read all event in the replay log");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
* replay-debugging.c
|
||||
*
|
||||
* Copyright (c) 2010-2020 Institute for System Programming
|
||||
* of the Russian Academy of Sciences.
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "system/replay.h"
|
||||
#include "system/runstate.h"
|
||||
#include "replay-internal.h"
|
||||
#include "monitor/hmp.h"
|
||||
#include "monitor/monitor.h"
|
||||
#include "qapi/qapi-commands-replay.h"
|
||||
#include "qobject/qdict.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "block/snapshot.h"
|
||||
#include "migration/snapshot.h"
|
||||
|
||||
static bool replay_is_debugging;
|
||||
static int64_t replay_last_breakpoint;
|
||||
static int64_t replay_last_snapshot;
|
||||
|
||||
bool replay_running_debug(void)
|
||||
{
|
||||
return replay_is_debugging;
|
||||
}
|
||||
|
||||
void hmp_info_replay(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
if (replay_mode == REPLAY_MODE_NONE) {
|
||||
monitor_printf(mon, "Record/replay is not active\n");
|
||||
} else {
|
||||
monitor_printf(mon,
|
||||
"%s execution '%s': instruction count = %"PRId64"\n",
|
||||
replay_mode == REPLAY_MODE_RECORD ? "Recording" : "Replaying",
|
||||
replay_get_filename(), replay_get_current_icount());
|
||||
}
|
||||
}
|
||||
|
||||
ReplayInfo *qmp_query_replay(Error **errp)
|
||||
{
|
||||
ReplayInfo *retval = g_new0(ReplayInfo, 1);
|
||||
|
||||
retval->mode = replay_mode;
|
||||
if (replay_get_filename()) {
|
||||
retval->filename = g_strdup(replay_get_filename());
|
||||
}
|
||||
retval->icount = replay_get_current_icount();
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void replay_break(uint64_t icount, QEMUTimerCB callback, void *opaque)
|
||||
{
|
||||
assert(replay_mode == REPLAY_MODE_PLAY);
|
||||
assert(replay_mutex_locked());
|
||||
assert(replay_break_icount >= replay_get_current_icount());
|
||||
assert(callback);
|
||||
|
||||
replay_break_icount = icount;
|
||||
|
||||
if (replay_break_timer) {
|
||||
timer_del(replay_break_timer);
|
||||
}
|
||||
replay_break_timer = timer_new_ns(QEMU_CLOCK_REALTIME,
|
||||
callback, opaque);
|
||||
}
|
||||
|
||||
static void replay_delete_break(void)
|
||||
{
|
||||
assert(replay_mode == REPLAY_MODE_PLAY);
|
||||
assert(replay_mutex_locked());
|
||||
|
||||
if (replay_break_timer) {
|
||||
timer_free(replay_break_timer);
|
||||
replay_break_timer = NULL;
|
||||
}
|
||||
replay_break_icount = -1ULL;
|
||||
}
|
||||
|
||||
static void replay_stop_vm(void *opaque)
|
||||
{
|
||||
vm_stop(RUN_STATE_PAUSED);
|
||||
replay_delete_break();
|
||||
}
|
||||
|
||||
void qmp_replay_break(int64_t icount, Error **errp)
|
||||
{
|
||||
if (replay_mode == REPLAY_MODE_PLAY) {
|
||||
if (icount >= replay_get_current_icount()) {
|
||||
replay_break(icount, replay_stop_vm, NULL);
|
||||
} else {
|
||||
error_setg(errp,
|
||||
"cannot set breakpoint at the instruction in the past");
|
||||
}
|
||||
} else {
|
||||
error_setg(errp, "setting the breakpoint is allowed only in play mode");
|
||||
}
|
||||
}
|
||||
|
||||
void hmp_replay_break(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
int64_t icount = qdict_get_try_int(qdict, "icount", -1LL);
|
||||
Error *err = NULL;
|
||||
|
||||
qmp_replay_break(icount, &err);
|
||||
if (err) {
|
||||
error_report_err(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void qmp_replay_delete_break(Error **errp)
|
||||
{
|
||||
if (replay_mode == REPLAY_MODE_PLAY) {
|
||||
replay_delete_break();
|
||||
} else {
|
||||
error_setg(errp, "replay breakpoints are allowed only in play mode");
|
||||
}
|
||||
}
|
||||
|
||||
void hmp_replay_delete_break(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
Error *err = NULL;
|
||||
|
||||
qmp_replay_delete_break(&err);
|
||||
if (err) {
|
||||
error_report_err(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static char *replay_find_nearest_snapshot(int64_t icount,
|
||||
int64_t *snapshot_icount)
|
||||
{
|
||||
BlockDriverState *bs;
|
||||
g_autofree QEMUSnapshotInfo *sn_tab = NULL;
|
||||
QEMUSnapshotInfo *nearest = NULL;
|
||||
int rv;
|
||||
int nb_sns, i;
|
||||
|
||||
*snapshot_icount = -1;
|
||||
|
||||
bs = bdrv_all_find_vmstate_bs(NULL, false, NULL, NULL);
|
||||
if (!bs) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
nb_sns = bdrv_snapshot_list(bs, &sn_tab);
|
||||
if (nb_sns < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < nb_sns; i++) {
|
||||
rv = bdrv_all_has_snapshot(sn_tab[i].name, false, NULL, NULL);
|
||||
if (rv < 0) {
|
||||
return NULL;
|
||||
}
|
||||
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) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*snapshot_icount = nearest->icount;
|
||||
return g_strdup(nearest->name);
|
||||
}
|
||||
|
||||
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. */
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* 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 "system/replay.h"
|
||||
#include "replay-internal.h"
|
||||
#include "qemu/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, (QemuInputEvent *)event->opaque);
|
||||
g_free(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);
|
||||
}
|
||||
}
|
||||
|
||||
/*! 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_new0(Event, 1);
|
||||
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);
|
||||
/* Kick the TCG thread out of tcg_cpu_exec(). */
|
||||
cpu_exit(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(QemuInputEvent *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)
|
||||
{
|
||||
if (replay_mode != REPLAY_MODE_PLAY) {
|
||||
/* put the event into the file */
|
||||
g_assert(event->event_kind < REPLAY_ASYNC_COUNT);
|
||||
replay_put_event(EVENT_ASYNC + 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(void)
|
||||
{
|
||||
g_assert(replay_mutex_locked());
|
||||
while (!QTAILQ_EMPTY(&events_list)) {
|
||||
Event *event = QTAILQ_FIRST(&events_list);
|
||||
replay_save_event(event);
|
||||
replay_run_event(event);
|
||||
QTAILQ_REMOVE(&events_list, event, events);
|
||||
g_free(event);
|
||||
}
|
||||
}
|
||||
|
||||
static Event *replay_read_event(void)
|
||||
{
|
||||
Event *event;
|
||||
ReplayAsyncEventKind event_kind = replay_state.data_kind - EVENT_ASYNC;
|
||||
|
||||
/* Events that has not to be in the queue */
|
||||
switch (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_new0(Event, 1);
|
||||
event->event_kind = event_kind;
|
||||
event->opaque = replay_read_input_event();
|
||||
return event;
|
||||
case REPLAY_ASYNC_EVENT_INPUT_SYNC:
|
||||
event = g_new0(Event, 1);
|
||||
event->event_kind = event_kind;
|
||||
event->opaque = 0;
|
||||
return event;
|
||||
case REPLAY_ASYNC_EVENT_CHAR_READ:
|
||||
event = g_new0(Event, 1);
|
||||
event->event_kind = 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_new0(Event, 1);
|
||||
event->event_kind = event_kind;
|
||||
event->opaque = replay_event_net_load();
|
||||
return event;
|
||||
default:
|
||||
error_report("Unknown ID %d of replay event", event_kind);
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
|
||||
QTAILQ_FOREACH(event, &events_list, events) {
|
||||
if (event->event_kind == event_kind
|
||||
&& (replay_state.read_event_id == -1
|
||||
|| replay_state.read_event_id == event->id)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (event) {
|
||||
QTAILQ_REMOVE(&events_list, event, events);
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
/* Called with replay mutex locked */
|
||||
void replay_read_events(void)
|
||||
{
|
||||
g_assert(replay_mutex_locked());
|
||||
while (replay_state.data_kind >= EVENT_ASYNC
|
||||
&& replay_state.data_kind <= EVENT_ASYNC_LAST) {
|
||||
Event *event = replay_read_event();
|
||||
if (!event) {
|
||||
break;
|
||||
}
|
||||
replay_finish_event();
|
||||
replay_state.read_event_id = -1;
|
||||
replay_run_event(event);
|
||||
|
||||
g_free(event);
|
||||
}
|
||||
}
|
||||
|
||||
void replay_init_events(void)
|
||||
{
|
||||
replay_state.read_event_id = -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;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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 "system/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(QemuInputEvent *evt)
|
||||
{
|
||||
replay_put_dword(evt->type);
|
||||
|
||||
switch (evt->type) {
|
||||
case INPUT_EVENT_KIND_KEY:
|
||||
replay_put_dword(evt->key.key);
|
||||
replay_put_byte(evt->key.down);
|
||||
break;
|
||||
case INPUT_EVENT_KIND_BTN:
|
||||
replay_put_dword(evt->btn.button);
|
||||
replay_put_byte(evt->btn.down);
|
||||
break;
|
||||
case INPUT_EVENT_KIND_REL:
|
||||
replay_put_dword(evt->rel.axis);
|
||||
replay_put_qword(evt->rel.value);
|
||||
break;
|
||||
case INPUT_EVENT_KIND_ABS:
|
||||
replay_put_dword(evt->abs.axis);
|
||||
replay_put_qword(evt->abs.value);
|
||||
break;
|
||||
case INPUT_EVENT_KIND_MTT:
|
||||
replay_put_dword(evt->mtt.type);
|
||||
replay_put_qword(evt->mtt.slot);
|
||||
replay_put_qword(evt->mtt.tracking_id);
|
||||
replay_put_dword(evt->mtt.axis);
|
||||
replay_put_qword(evt->mtt.value);
|
||||
break;
|
||||
case INPUT_EVENT_KIND__MAX:
|
||||
/* keep gcc happy */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QemuInputEvent *replay_read_input_event(void)
|
||||
{
|
||||
QemuInputEvent *evt = g_new(QemuInputEvent, 1);
|
||||
|
||||
evt->type = replay_get_dword();
|
||||
switch (evt->type) {
|
||||
case INPUT_EVENT_KIND_KEY:
|
||||
evt->key.key = replay_get_dword();
|
||||
evt->key.down = replay_get_byte();
|
||||
break;
|
||||
case INPUT_EVENT_KIND_BTN:
|
||||
evt->btn.button = (InputButton)replay_get_dword();
|
||||
evt->btn.down = replay_get_byte();
|
||||
break;
|
||||
case INPUT_EVENT_KIND_REL:
|
||||
evt->rel.axis = (InputAxis)replay_get_dword();
|
||||
evt->rel.value = replay_get_qword();
|
||||
break;
|
||||
case INPUT_EVENT_KIND_ABS:
|
||||
evt->abs.axis = (InputAxis)replay_get_dword();
|
||||
evt->abs.value = replay_get_qword();
|
||||
break;
|
||||
case INPUT_EVENT_KIND_MTT:
|
||||
evt->mtt.type = (InputMultiTouchType)replay_get_dword();
|
||||
evt->mtt.slot = replay_get_qword();
|
||||
evt->mtt.tracking_id = replay_get_qword();
|
||||
evt->mtt.axis = (InputAxis)replay_get_dword();
|
||||
evt->mtt.value = replay_get_qword();
|
||||
break;
|
||||
case INPUT_EVENT_KIND__MAX:
|
||||
/* keep gcc happy */
|
||||
break;
|
||||
}
|
||||
|
||||
return evt;
|
||||
}
|
||||
|
||||
void replay_input_event(QemuConsole *src, QemuInputEvent *evt)
|
||||
{
|
||||
if (replay_mode == REPLAY_MODE_PLAY) {
|
||||
/* Nothing */
|
||||
} else if (replay_mode == REPLAY_MODE_RECORD) {
|
||||
QemuInputEvent *clone = g_new(QemuInputEvent, 1);
|
||||
*clone = *evt;
|
||||
replay_add_input_event(clone);
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* 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 "system/replay.h"
|
||||
#include "system/runstate.h"
|
||||
#include "replay-internal.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "trace.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);
|
||||
}
|
||||
|
||||
static void replay_putc(uint8_t byte)
|
||||
{
|
||||
if (replay_file) {
|
||||
if (putc(byte, replay_file) == EOF) {
|
||||
replay_write_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void replay_put_byte(uint8_t byte)
|
||||
{
|
||||
trace_replay_put_byte(byte);
|
||||
replay_putc(byte);
|
||||
}
|
||||
|
||||
void replay_put_event(uint8_t event)
|
||||
{
|
||||
trace_replay_put_event(event);
|
||||
assert(event < EVENT_COUNT);
|
||||
replay_putc(event);
|
||||
}
|
||||
|
||||
|
||||
void replay_put_word(uint16_t word)
|
||||
{
|
||||
trace_replay_put_word(word);
|
||||
replay_putc(word >> 8);
|
||||
replay_putc(word);
|
||||
}
|
||||
|
||||
void replay_put_dword(uint32_t dword)
|
||||
{
|
||||
int i;
|
||||
|
||||
trace_replay_put_dword(dword);
|
||||
for (i = 24; i >= 0; i -= 8) {
|
||||
replay_putc(dword >> i);
|
||||
}
|
||||
}
|
||||
|
||||
void replay_put_qword(int64_t qword)
|
||||
{
|
||||
int i;
|
||||
|
||||
trace_replay_put_qword(qword);
|
||||
for (i = 56; i >= 0; i -= 8) {
|
||||
replay_putc(qword >> i);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t replay_getc(void)
|
||||
{
|
||||
uint8_t byte = 0;
|
||||
if (replay_file) {
|
||||
int r = getc(replay_file);
|
||||
if (r == EOF) {
|
||||
replay_read_error();
|
||||
}
|
||||
byte = r;
|
||||
}
|
||||
return byte;
|
||||
}
|
||||
|
||||
uint8_t replay_get_byte(void)
|
||||
{
|
||||
uint8_t byte = replay_getc();
|
||||
trace_replay_get_byte(byte);
|
||||
return byte;
|
||||
}
|
||||
|
||||
uint16_t replay_get_word(void)
|
||||
{
|
||||
uint16_t word = 0;
|
||||
if (replay_file) {
|
||||
word = replay_getc();
|
||||
word = (word << 8) + replay_getc();
|
||||
}
|
||||
|
||||
trace_replay_get_word(word);
|
||||
return word;
|
||||
}
|
||||
|
||||
uint32_t replay_get_dword(void)
|
||||
{
|
||||
uint32_t dword = 0;
|
||||
int i;
|
||||
|
||||
if (replay_file) {
|
||||
for (i = 24; i >= 0; i -= 8) {
|
||||
dword |= replay_getc() << i;
|
||||
}
|
||||
}
|
||||
|
||||
trace_replay_get_dword(dword);
|
||||
return dword;
|
||||
}
|
||||
|
||||
int64_t replay_get_qword(void)
|
||||
{
|
||||
uint64_t qword = 0;
|
||||
int i;
|
||||
|
||||
if (replay_file) {
|
||||
for (i = 56; i >= 0; i -= 8) {
|
||||
qword |= (uint64_t)replay_getc() << i;
|
||||
}
|
||||
}
|
||||
|
||||
trace_replay_get_qword(qword);
|
||||
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)
|
||||
{
|
||||
trace_replay_fetch_data_kind();
|
||||
if (replay_file) {
|
||||
if (!replay_state.has_unread_data) {
|
||||
replay_state.data_kind = replay_getc();
|
||||
replay_state.current_event++;
|
||||
trace_replay_get_event(replay_state.current_event, replay_state.data_kind);
|
||||
if (replay_state.data_kind == EVENT_INSTRUCTION) {
|
||||
replay_state.instruction_count = replay_get_dword();
|
||||
}
|
||||
replay_check_error();
|
||||
replay_state.has_unread_data = true;
|
||||
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 = false;
|
||||
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(!bql_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 */
|
||||
trace_replay_advance_current_icount(replay_state.current_icount, diff);
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
#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.
|
||||
*
|
||||
*/
|
||||
|
||||
/* Asynchronous events IDs */
|
||||
|
||||
typedef 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
|
||||
} ReplayAsyncEventKind;
|
||||
|
||||
/*
|
||||
* Any changes to order/number of events will need to bump
|
||||
* REPLAY_VERSION to prevent confusion with old logs. Also don't
|
||||
* forget to update replay_event_name() to make your debugging life
|
||||
* easier.
|
||||
*/
|
||||
enum ReplayEvents {
|
||||
/* for instruction event */
|
||||
EVENT_INSTRUCTION,
|
||||
/* for software interrupt */
|
||||
EVENT_INTERRUPT,
|
||||
/* for emulated exceptions */
|
||||
EVENT_EXCEPTION,
|
||||
/* for async events */
|
||||
EVENT_ASYNC,
|
||||
EVENT_ASYNC_LAST = EVENT_ASYNC + REPLAY_ASYNC_COUNT - 1,
|
||||
/* 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
|
||||
};
|
||||
|
||||
/**
|
||||
* typedef ReplayState - global tracking Replay state
|
||||
*
|
||||
* This structure tracks where we are in the current ReplayState
|
||||
* including the logged events from the recorded replay stream. Some
|
||||
* of the data is also stored/restored from VMStateDescription when VM
|
||||
* save/restore events take place.
|
||||
*
|
||||
* @cached_clock: Cached clocks values
|
||||
* @current_icount: number of processed instructions
|
||||
* @instruction_count: number of instructions until next event
|
||||
* @current_event: current event index
|
||||
* @data_kind: current event
|
||||
* @has_unread_data: true if event not yet processed
|
||||
* @file_offset: offset into replay log at replay snapshot
|
||||
* @block_request_id: current serialised block request id
|
||||
* @read_event_id: current async read event id
|
||||
* @n_audio_samples: expected audio samples
|
||||
*/
|
||||
typedef struct ReplayState {
|
||||
int64_t cached_clock[REPLAY_CLOCK_COUNT];
|
||||
uint64_t current_icount;
|
||||
int instruction_count;
|
||||
unsigned int current_event;
|
||||
unsigned int data_kind;
|
||||
bool has_unread_data;
|
||||
uint64_t file_offset;
|
||||
uint64_t block_request_id;
|
||||
uint64_t read_event_id;
|
||||
size_t n_audio_samples;
|
||||
} 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(ReplayClockKind 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(void);
|
||||
/*! Read events from the file into the input queue */
|
||||
void replay_read_events(void);
|
||||
/*! 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(QemuInputEvent *evt);
|
||||
/*! Reads input event from the log */
|
||||
QemuInputEvent *replay_read_input_event(void);
|
||||
/*! Adds input event to the queue */
|
||||
void replay_add_input_event(QemuInputEvent *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);
|
||||
|
||||
/* Diagnostics */
|
||||
|
||||
/**
|
||||
* replay_sync_error(): report sync error and exit
|
||||
*
|
||||
* When we reach an error condition we want to report it centrally so
|
||||
* we can also dump some useful information into the logs.
|
||||
*/
|
||||
G_NORETURN void replay_sync_error(const char *error);
|
||||
|
||||
/* 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
|
||||
@@ -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 "system/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;
|
||||
}
|
||||
@@ -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 "system/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;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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 "system/replay.h"
|
||||
#include "replay-internal.h"
|
||||
#include "monitor/monitor.h"
|
||||
#include "qobject/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 = 3,
|
||||
.minimum_version_id = 3,
|
||||
.pre_save = replay_pre_save,
|
||||
.post_load = replay_post_load,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_INT64_ARRAY(cached_clock, ReplayState, REPLAY_CLOCK_COUNT),
|
||||
VMSTATE_UINT64(current_icount, ReplayState),
|
||||
VMSTATE_INT32(instruction_count, ReplayState),
|
||||
VMSTATE_UINT32(current_event, ReplayState),
|
||||
VMSTATE_UINT32(data_kind, ReplayState),
|
||||
VMSTATE_BOOL(has_unread_data, ReplayState),
|
||||
VMSTATE_UINT64(file_offset, ReplayState),
|
||||
VMSTATE_UINT64(block_request_id, ReplayState),
|
||||
VMSTATE_UINT64(read_event_id, 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();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 "system/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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
return replay_state.cached_clock[kind];
|
||||
}
|
||||
+505
@@ -0,0 +1,505 @@
|
||||
/*
|
||||
* 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 "exec/icount.h"
|
||||
#include "system/replay.h"
|
||||
#include "system/runstate.h"
|
||||
#include "replay-internal.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qemu/option.h"
|
||||
#include "system/cpus.h"
|
||||
#include "qemu/error-report.h"
|
||||
|
||||
/* Current version of the replay mechanism.
|
||||
Increase it when file format changes. */
|
||||
#define REPLAY_VERSION 0xe0200e
|
||||
/* 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;
|
||||
|
||||
/* Pretty print event names */
|
||||
|
||||
static const char *replay_async_event_name(ReplayAsyncEventKind event)
|
||||
{
|
||||
switch (event) {
|
||||
#define ASYNC_EVENT(_x) case REPLAY_ASYNC_EVENT_ ## _x: return "ASYNC_EVENT_"#_x
|
||||
ASYNC_EVENT(BH);
|
||||
ASYNC_EVENT(BH_ONESHOT);
|
||||
ASYNC_EVENT(INPUT);
|
||||
ASYNC_EVENT(INPUT_SYNC);
|
||||
ASYNC_EVENT(CHAR_READ);
|
||||
ASYNC_EVENT(BLOCK);
|
||||
ASYNC_EVENT(NET);
|
||||
#undef ASYNC_EVENT
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
static const char *replay_clock_event_name(ReplayClockKind clock)
|
||||
{
|
||||
switch (clock) {
|
||||
#define CLOCK_EVENT(_x) case REPLAY_CLOCK_ ## _x: return "CLOCK_" #_x
|
||||
CLOCK_EVENT(HOST);
|
||||
CLOCK_EVENT(VIRTUAL_RT);
|
||||
#undef CLOCK_EVENT
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
/* Pretty print shutdown event names */
|
||||
static const char *replay_shutdown_event_name(ShutdownCause cause)
|
||||
{
|
||||
switch (cause) {
|
||||
#define SHUTDOWN_EVENT(_x) case SHUTDOWN_CAUSE_ ## _x: return "SHUTDOWN_CAUSE_" #_x
|
||||
SHUTDOWN_EVENT(NONE);
|
||||
SHUTDOWN_EVENT(HOST_ERROR);
|
||||
SHUTDOWN_EVENT(HOST_QMP_QUIT);
|
||||
SHUTDOWN_EVENT(HOST_QMP_SYSTEM_RESET);
|
||||
SHUTDOWN_EVENT(HOST_SIGNAL);
|
||||
SHUTDOWN_EVENT(HOST_UI);
|
||||
SHUTDOWN_EVENT(GUEST_SHUTDOWN);
|
||||
SHUTDOWN_EVENT(GUEST_RESET);
|
||||
SHUTDOWN_EVENT(GUEST_PANIC);
|
||||
SHUTDOWN_EVENT(SUBSYSTEM_RESET);
|
||||
SHUTDOWN_EVENT(SNAPSHOT_LOAD);
|
||||
#undef SHUTDOWN_EVENT
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
static const char *replay_checkpoint_event_name(enum ReplayCheckpoint checkpoint)
|
||||
{
|
||||
switch (checkpoint) {
|
||||
#define CHECKPOINT_EVENT(_x) case CHECKPOINT_ ## _x: return "CHECKPOINT_" #_x
|
||||
CHECKPOINT_EVENT(CLOCK_WARP_START);
|
||||
CHECKPOINT_EVENT(CLOCK_WARP_ACCOUNT);
|
||||
CHECKPOINT_EVENT(RESET_REQUESTED);
|
||||
CHECKPOINT_EVENT(SUSPEND_REQUESTED);
|
||||
CHECKPOINT_EVENT(CLOCK_VIRTUAL);
|
||||
CHECKPOINT_EVENT(CLOCK_HOST);
|
||||
CHECKPOINT_EVENT(CLOCK_VIRTUAL_RT);
|
||||
CHECKPOINT_EVENT(INIT);
|
||||
CHECKPOINT_EVENT(RESET);
|
||||
#undef CHECKPOINT_EVENT
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
static const char *replay_event_name(enum ReplayEvents event)
|
||||
{
|
||||
/* First deal with the simple ones */
|
||||
switch (event) {
|
||||
#define EVENT(_x) case EVENT_ ## _x: return "EVENT_"#_x
|
||||
EVENT(INSTRUCTION);
|
||||
EVENT(INTERRUPT);
|
||||
EVENT(EXCEPTION);
|
||||
EVENT(CHAR_WRITE);
|
||||
EVENT(CHAR_READ_ALL);
|
||||
EVENT(AUDIO_OUT);
|
||||
EVENT(AUDIO_IN);
|
||||
EVENT(RANDOM);
|
||||
#undef EVENT
|
||||
default:
|
||||
if (event >= EVENT_ASYNC && event <= EVENT_ASYNC_LAST) {
|
||||
return replay_async_event_name(event - EVENT_ASYNC);
|
||||
} else if (event >= EVENT_SHUTDOWN && event <= EVENT_SHUTDOWN_LAST) {
|
||||
return replay_shutdown_event_name(event - EVENT_SHUTDOWN);
|
||||
} else if (event >= EVENT_CLOCK && event <= EVENT_CLOCK_LAST) {
|
||||
return replay_clock_event_name(event - EVENT_CLOCK);
|
||||
} else if (event >= EVENT_CHECKPOINT && event <= EVENT_CHECKPOINT_LAST) {
|
||||
return replay_checkpoint_event_name(event - EVENT_CHECKPOINT);
|
||||
}
|
||||
}
|
||||
|
||||
g_assert_not_reached();
|
||||
}
|
||||
|
||||
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;
|
||||
g_assert(replay_mutex_locked());
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
replay_save_instructions();
|
||||
|
||||
if (replay_mode == REPLAY_MODE_RECORD) {
|
||||
g_assert(replay_mutex_locked());
|
||||
replay_put_event(EVENT_SHUTDOWN + cause);
|
||||
}
|
||||
}
|
||||
|
||||
bool replay_checkpoint(ReplayCheckpoint checkpoint)
|
||||
{
|
||||
assert(EVENT_CHECKPOINT + checkpoint <= EVENT_CHECKPOINT_LAST);
|
||||
|
||||
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 {
|
||||
return false;
|
||||
}
|
||||
} else if (replay_mode == REPLAY_MODE_RECORD) {
|
||||
g_assert(replay_mutex_locked());
|
||||
replay_put_event(EVENT_CHECKPOINT + checkpoint);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void replay_async_events(void)
|
||||
{
|
||||
static bool processing = false;
|
||||
/*
|
||||
* If we are already processing the events, recursion may occur
|
||||
* in case of incorrect implementation when HW event modifies timers.
|
||||
* Timer modification may invoke the icount warp, event processing,
|
||||
* and cause the recursion.
|
||||
*/
|
||||
g_assert(!processing);
|
||||
processing = true;
|
||||
|
||||
replay_save_instructions();
|
||||
|
||||
if (replay_mode == REPLAY_MODE_PLAY) {
|
||||
g_assert(replay_mutex_locked());
|
||||
replay_read_events();
|
||||
} else if (replay_mode == REPLAY_MODE_RECORD) {
|
||||
g_assert(replay_mutex_locked());
|
||||
replay_save_events();
|
||||
}
|
||||
processing = false;
|
||||
}
|
||||
|
||||
bool replay_has_event(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;
|
||||
res = res || (EVENT_ASYNC <= replay_state.data_kind
|
||||
&& replay_state.data_kind <= EVENT_ASYNC_LAST);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
G_NORETURN void replay_sync_error(const char *error)
|
||||
{
|
||||
error_report("%s (insn total %"PRId64"/%d left, event %d is %s)", error,
|
||||
replay_state.current_icount, replay_state.instruction_count,
|
||||
replay_state.current_event,
|
||||
replay_event_name(replay_state.data_kind));
|
||||
abort();
|
||||
}
|
||||
|
||||
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.current_event = 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();
|
||||
}
|
||||
|
||||
runstate_replay_enable();
|
||||
|
||||
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;
|
||||
}
|
||||
g_free(replay_filename);
|
||||
replay_filename = NULL;
|
||||
|
||||
g_free(replay_snapshot);
|
||||
replay_snapshot = NULL;
|
||||
|
||||
replay_finish_events();
|
||||
replay_mode = REPLAY_MODE_NONE;
|
||||
}
|
||||
|
||||
void replay_add_blocker(const char *feature)
|
||||
{
|
||||
Error *reason = NULL;
|
||||
|
||||
error_setg(&reason, "Record/replay is not supported with %s",
|
||||
feature);
|
||||
replay_blockers = g_slist_prepend(replay_blockers, reason);
|
||||
}
|
||||
|
||||
const char *replay_get_filename(void)
|
||||
{
|
||||
return replay_filename;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "system/replay.h"
|
||||
#include "ui/input.h"
|
||||
|
||||
void replay_input_event(QemuConsole *src, QemuInputEvent *evt)
|
||||
{
|
||||
qemu_input_event_send_impl(src, evt);
|
||||
}
|
||||
|
||||
void replay_input_sync_event(void)
|
||||
{
|
||||
qemu_input_event_sync_impl();
|
||||
}
|
||||
|
||||
void replay_add_blocker(const char *feature)
|
||||
{
|
||||
}
|
||||
void replay_audio_in_start(size_t *nsamples)
|
||||
{
|
||||
}
|
||||
void replay_audio_in_sample_lr(uint64_t *left, uint64_t *right)
|
||||
{
|
||||
}
|
||||
void replay_audio_in_finish(void)
|
||||
{
|
||||
}
|
||||
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");
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
replay_put_byte(uint8_t event) "%02x"
|
||||
replay_put_event(uint8_t event) "%02x"
|
||||
replay_put_word(uint16_t event) "%04x"
|
||||
replay_put_dword(uint32_t event) "%08x"
|
||||
replay_put_qword(uint64_t event) "%016" PRIx64
|
||||
replay_get_byte(uint8_t byte) "%02x"
|
||||
replay_get_word(uint16_t word) "%04x"
|
||||
replay_get_dword(uint32_t dword) "%08x"
|
||||
replay_get_qword(uint64_t qword) "%016" PRIx64
|
||||
replay_fetch_data_kind(void) ""
|
||||
replay_get_event(uint32_t current, uint8_t data) "#%u data=%02x"
|
||||
replay_advance_current_icount(uint64_t current_icount, int diff) "current=%" PRIu64 " diff=%d"
|
||||
@@ -0,0 +1 @@
|
||||
#include "trace/trace-replay.h"
|
||||
Reference in New Issue
Block a user