So here it is, let's see what happens.
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQEcBAABCAAGBQJWPHM6AAoJEL/70l94x66DK5YIAJTNthYWL8eNhQ1iek6CLlV+ etVXm3JDmkV0zOfYVHLBb44VLZ6I1ocas+57F/kmz7SKpMLiI6bMXRxhTSkiO4D+ 3N36cWQf3fq+P0DmxuikMlYGz8V6QQ5PQE2xJKV0ZIWAkiqInxilkN3qt81sNR+A A9Ohom3sc0eGHyYJcVDK4krbnNSAZjIB2yMWperw61x+GYAhxjA02HPUgB32KK6q KrdnKmnRu9Cw6y4wTCbbDITJztPexZYsX2DOJh30wC0eNcE+MZ7J2im8Frpxe+Ml C8MUuvSqLOyeu9tUfrXGzd6kMtEKrmU+fh2nNbxJbtfowDjkW2jcIEgC0UjkGE4= =BF1q -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/bonzini/tags/for-upstream-replay' into staging So here it is, let's see what happens. # gpg: Signature made Fri 06 Nov 2015 09:30:34 GMT using RSA key ID 78C7AE83 # gpg: Good signature from "Paolo Bonzini <bonzini@gnu.org>" # gpg: aka "Paolo Bonzini <pbonzini@redhat.com>" * remotes/bonzini/tags/for-upstream-replay: replay: recording of the user input replay: command line options replay: replay blockers for devices replay: initialization and deinitialization replay: ptimer bottom halves: introduce bh call function replay: checkpoints icount: improve counting for record/replay replay: shutdown event replay: recording and replaying clock ticks replay: asynchronous events infrastructure replay: interrupts and exceptions cpu: replay instructions sequence cpu-exec: allow temporary disabling icount replay: introduce icount event replay: introduce mutex to protect the replay log replay: internal functions for replay log replay: global variables and function stubs Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
9319738080
@ -54,6 +54,8 @@ common-obj-y += audio/
|
||||
common-obj-y += hw/
|
||||
common-obj-y += accel.o
|
||||
|
||||
common-obj-y += replay/
|
||||
|
||||
common-obj-y += ui/
|
||||
common-obj-y += bt-host.o bt-vhci.o
|
||||
bt-host.o-cflags := $(BLUEZ_CFLAGS)
|
||||
|
7
async.c
7
async.c
@ -59,6 +59,11 @@ QEMUBH *aio_bh_new(AioContext *ctx, QEMUBHFunc *cb, void *opaque)
|
||||
return bh;
|
||||
}
|
||||
|
||||
void aio_bh_call(QEMUBH *bh)
|
||||
{
|
||||
bh->cb(bh->opaque);
|
||||
}
|
||||
|
||||
/* Multiple occurrences of aio_bh_poll cannot be called concurrently */
|
||||
int aio_bh_poll(AioContext *ctx)
|
||||
{
|
||||
@ -84,7 +89,7 @@ int aio_bh_poll(AioContext *ctx)
|
||||
ret = 1;
|
||||
}
|
||||
bh->idle = 0;
|
||||
bh->cb(bh->opaque);
|
||||
aio_bh_call(bh);
|
||||
}
|
||||
}
|
||||
|
||||
|
55
cpu-exec.c
55
cpu-exec.c
@ -30,6 +30,7 @@
|
||||
#if defined(TARGET_I386) && !defined(CONFIG_USER_ONLY)
|
||||
#include "hw/i386/apic.h"
|
||||
#endif
|
||||
#include "sysemu/replay.h"
|
||||
|
||||
/* -icount align implementation. */
|
||||
|
||||
@ -184,7 +185,7 @@ static inline tcg_target_ulong cpu_tb_exec(CPUState *cpu, uint8_t *tb_ptr)
|
||||
/* Execute the code without caching the generated code. An interpreter
|
||||
could be used if available. */
|
||||
static void cpu_exec_nocache(CPUState *cpu, int max_cycles,
|
||||
TranslationBlock *orig_tb)
|
||||
TranslationBlock *orig_tb, bool ignore_icount)
|
||||
{
|
||||
TranslationBlock *tb;
|
||||
|
||||
@ -194,7 +195,8 @@ static void cpu_exec_nocache(CPUState *cpu, int max_cycles,
|
||||
max_cycles = CF_COUNT_MASK;
|
||||
|
||||
tb = tb_gen_code(cpu, orig_tb->pc, orig_tb->cs_base, orig_tb->flags,
|
||||
max_cycles | CF_NOCACHE);
|
||||
max_cycles | CF_NOCACHE
|
||||
| (ignore_icount ? CF_IGNORE_ICOUNT : 0));
|
||||
tb->orig_tb = tcg_ctx.tb_ctx.tb_invalidated_flag ? NULL : orig_tb;
|
||||
cpu->current_tb = tb;
|
||||
/* execute the generated code */
|
||||
@ -345,21 +347,25 @@ int cpu_exec(CPUState *cpu)
|
||||
uintptr_t next_tb;
|
||||
SyncClocks sc;
|
||||
|
||||
/* replay_interrupt may need current_cpu */
|
||||
current_cpu = cpu;
|
||||
|
||||
if (cpu->halted) {
|
||||
#if defined(TARGET_I386) && !defined(CONFIG_USER_ONLY)
|
||||
if (cpu->interrupt_request & CPU_INTERRUPT_POLL) {
|
||||
if ((cpu->interrupt_request & CPU_INTERRUPT_POLL)
|
||||
&& replay_interrupt()) {
|
||||
apic_poll_irq(x86_cpu->apic_state);
|
||||
cpu_reset_interrupt(cpu, CPU_INTERRUPT_POLL);
|
||||
}
|
||||
#endif
|
||||
if (!cpu_has_work(cpu)) {
|
||||
current_cpu = NULL;
|
||||
return EXCP_HALTED;
|
||||
}
|
||||
|
||||
cpu->halted = 0;
|
||||
}
|
||||
|
||||
current_cpu = cpu;
|
||||
atomic_mb_set(&tcg_current_cpu, cpu);
|
||||
rcu_read_lock();
|
||||
|
||||
@ -401,10 +407,22 @@ int cpu_exec(CPUState *cpu)
|
||||
cpu->exception_index = -1;
|
||||
break;
|
||||
#else
|
||||
cc->do_interrupt(cpu);
|
||||
cpu->exception_index = -1;
|
||||
if (replay_exception()) {
|
||||
cc->do_interrupt(cpu);
|
||||
cpu->exception_index = -1;
|
||||
} else if (!replay_has_interrupt()) {
|
||||
/* give a chance to iothread in replay mode */
|
||||
ret = EXCP_INTERRUPT;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} else if (replay_has_exception()
|
||||
&& cpu->icount_decr.u16.low + cpu->icount_extra == 0) {
|
||||
/* try to cause an exception pending in the log */
|
||||
cpu_exec_nocache(cpu, 1, tb_find_fast(cpu), true);
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
next_tb = 0; /* force lookup of first TB */
|
||||
@ -420,30 +438,40 @@ int cpu_exec(CPUState *cpu)
|
||||
cpu->exception_index = EXCP_DEBUG;
|
||||
cpu_loop_exit(cpu);
|
||||
}
|
||||
if (interrupt_request & CPU_INTERRUPT_HALT) {
|
||||
if (replay_mode == REPLAY_MODE_PLAY
|
||||
&& !replay_has_interrupt()) {
|
||||
/* Do nothing */
|
||||
} else if (interrupt_request & CPU_INTERRUPT_HALT) {
|
||||
replay_interrupt();
|
||||
cpu->interrupt_request &= ~CPU_INTERRUPT_HALT;
|
||||
cpu->halted = 1;
|
||||
cpu->exception_index = EXCP_HLT;
|
||||
cpu_loop_exit(cpu);
|
||||
}
|
||||
#if defined(TARGET_I386)
|
||||
if (interrupt_request & CPU_INTERRUPT_INIT) {
|
||||
else if (interrupt_request & CPU_INTERRUPT_INIT) {
|
||||
replay_interrupt();
|
||||
cpu_svm_check_intercept_param(env, SVM_EXIT_INIT, 0);
|
||||
do_cpu_init(x86_cpu);
|
||||
cpu->exception_index = EXCP_HALTED;
|
||||
cpu_loop_exit(cpu);
|
||||
}
|
||||
#else
|
||||
if (interrupt_request & CPU_INTERRUPT_RESET) {
|
||||
else if (interrupt_request & CPU_INTERRUPT_RESET) {
|
||||
replay_interrupt();
|
||||
cpu_reset(cpu);
|
||||
cpu_loop_exit(cpu);
|
||||
}
|
||||
#endif
|
||||
/* The target hook has 3 exit conditions:
|
||||
False when the interrupt isn't processed,
|
||||
True when it is, and we should restart on a new TB,
|
||||
and via longjmp via cpu_loop_exit. */
|
||||
if (cc->cpu_exec_interrupt(cpu, interrupt_request)) {
|
||||
next_tb = 0;
|
||||
else {
|
||||
replay_interrupt();
|
||||
if (cc->cpu_exec_interrupt(cpu, interrupt_request)) {
|
||||
next_tb = 0;
|
||||
}
|
||||
}
|
||||
/* Don't use the cached interrupt_request value,
|
||||
do_interrupt may have updated the EXITTB flag. */
|
||||
@ -454,7 +482,8 @@ int cpu_exec(CPUState *cpu)
|
||||
next_tb = 0;
|
||||
}
|
||||
}
|
||||
if (unlikely(cpu->exit_request)) {
|
||||
if (unlikely(cpu->exit_request
|
||||
|| replay_has_interrupt())) {
|
||||
cpu->exit_request = 0;
|
||||
cpu->exception_index = EXCP_INTERRUPT;
|
||||
cpu_loop_exit(cpu);
|
||||
@ -519,7 +548,7 @@ int cpu_exec(CPUState *cpu)
|
||||
if (insns_left > 0) {
|
||||
/* Execute remaining instructions. */
|
||||
tb = (TranslationBlock *)(next_tb & ~TB_EXIT_MASK);
|
||||
cpu_exec_nocache(cpu, insns_left, tb);
|
||||
cpu_exec_nocache(cpu, insns_left, tb, false);
|
||||
align_clocks(&sc, cpu);
|
||||
}
|
||||
cpu->exception_index = EXCP_INTERRUPT;
|
||||
|
64
cpus.c
64
cpus.c
@ -42,6 +42,7 @@
|
||||
#include "qemu/seqlock.h"
|
||||
#include "qapi-event.h"
|
||||
#include "hw/nmi.h"
|
||||
#include "sysemu/replay.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include "qemu/compatfd.h"
|
||||
@ -334,7 +335,7 @@ static int64_t qemu_icount_round(int64_t count)
|
||||
return (count + (1 << icount_time_shift) - 1) >> icount_time_shift;
|
||||
}
|
||||
|
||||
static void icount_warp_rt(void *opaque)
|
||||
static void icount_warp_rt(void)
|
||||
{
|
||||
/* The icount_warp_timer is rescheduled soon after vm_clock_warp_start
|
||||
* changes from -1 to another value, so the race here is okay.
|
||||
@ -345,7 +346,8 @@ static void icount_warp_rt(void *opaque)
|
||||
|
||||
seqlock_write_lock(&timers_state.vm_clock_seqlock);
|
||||
if (runstate_is_running()) {
|
||||
int64_t clock = cpu_get_clock_locked();
|
||||
int64_t clock = REPLAY_CLOCK(REPLAY_CLOCK_VIRTUAL_RT,
|
||||
cpu_get_clock_locked());
|
||||
int64_t warp_delta;
|
||||
|
||||
warp_delta = clock - vm_clock_warp_start;
|
||||
@ -368,6 +370,11 @@ static void icount_warp_rt(void *opaque)
|
||||
}
|
||||
}
|
||||
|
||||
static void icount_dummy_timer(void *opaque)
|
||||
{
|
||||
(void)opaque;
|
||||
}
|
||||
|
||||
void qtest_clock_warp(int64_t dest)
|
||||
{
|
||||
int64_t clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
@ -403,6 +410,18 @@ void qemu_clock_warp(QEMUClockType type)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Nothing to do if the VM is stopped: QEMU_CLOCK_VIRTUAL timers
|
||||
* do not fire, so computing the deadline does not make sense.
|
||||
*/
|
||||
if (!runstate_is_running()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* warp clock deterministically in record/replay mode */
|
||||
if (!replay_checkpoint(CHECKPOINT_CLOCK_WARP)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (icount_sleep) {
|
||||
/*
|
||||
* If the CPUs have been sleeping, advance QEMU_CLOCK_VIRTUAL timer now.
|
||||
@ -412,7 +431,7 @@ void qemu_clock_warp(QEMUClockType type)
|
||||
* the CPU starts running, in case the CPU is woken by an event other
|
||||
* than the earliest QEMU_CLOCK_VIRTUAL timer.
|
||||
*/
|
||||
icount_warp_rt(NULL);
|
||||
icount_warp_rt();
|
||||
timer_del(icount_warp_timer);
|
||||
}
|
||||
if (!all_cpu_threads_idle()) {
|
||||
@ -605,7 +624,7 @@ void configure_icount(QemuOpts *opts, Error **errp)
|
||||
icount_sleep = qemu_opt_get_bool(opts, "sleep", true);
|
||||
if (icount_sleep) {
|
||||
icount_warp_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL_RT,
|
||||
icount_warp_rt, NULL);
|
||||
icount_dummy_timer, NULL);
|
||||
}
|
||||
|
||||
icount_align_option = qemu_opt_get_bool(opts, "align", false);
|
||||
@ -1402,6 +1421,28 @@ int vm_stop_force_state(RunState state)
|
||||
}
|
||||
}
|
||||
|
||||
static int64_t tcg_get_icount_limit(void)
|
||||
{
|
||||
int64_t deadline;
|
||||
|
||||
if (replay_mode != REPLAY_MODE_PLAY) {
|
||||
deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL);
|
||||
|
||||
/* Maintain prior (possibly buggy) behaviour where if no deadline
|
||||
* was set (as there is no QEMU_CLOCK_VIRTUAL timer) or it is more than
|
||||
* INT32_MAX nanoseconds ahead, we still use INT32_MAX
|
||||
* nanoseconds.
|
||||
*/
|
||||
if ((deadline < 0) || (deadline > INT32_MAX)) {
|
||||
deadline = INT32_MAX;
|
||||
}
|
||||
|
||||
return qemu_icount_round(deadline);
|
||||
} else {
|
||||
return replay_get_instructions();
|
||||
}
|
||||
}
|
||||
|
||||
static int tcg_cpu_exec(CPUState *cpu)
|
||||
{
|
||||
int ret;
|
||||
@ -1414,24 +1455,12 @@ static int tcg_cpu_exec(CPUState *cpu)
|
||||
#endif
|
||||
if (use_icount) {
|
||||
int64_t count;
|
||||
int64_t deadline;
|
||||
int decr;
|
||||
timers_state.qemu_icount -= (cpu->icount_decr.u16.low
|
||||
+ cpu->icount_extra);
|
||||
cpu->icount_decr.u16.low = 0;
|
||||
cpu->icount_extra = 0;
|
||||
deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL);
|
||||
|
||||
/* Maintain prior (possibly buggy) behaviour where if no deadline
|
||||
* was set (as there is no QEMU_CLOCK_VIRTUAL timer) or it is more than
|
||||
* INT32_MAX nanoseconds ahead, we still use INT32_MAX
|
||||
* nanoseconds.
|
||||
*/
|
||||
if ((deadline < 0) || (deadline > INT32_MAX)) {
|
||||
deadline = INT32_MAX;
|
||||
}
|
||||
|
||||
count = qemu_icount_round(deadline);
|
||||
count = tcg_get_icount_limit();
|
||||
timers_state.qemu_icount += count;
|
||||
decr = (count > 0xffff) ? 0xffff : count;
|
||||
count -= decr;
|
||||
@ -1449,6 +1478,7 @@ static int tcg_cpu_exec(CPUState *cpu)
|
||||
+ cpu->icount_extra);
|
||||
cpu->icount_decr.u32 = 0;
|
||||
cpu->icount_extra = 0;
|
||||
replay_account_executed_instructions();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
168
docs/replay.txt
Normal file
168
docs/replay.txt
Normal file
@ -0,0 +1,168 @@
|
||||
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.
|
||||
|
||||
Record/replay
|
||||
-------------
|
||||
|
||||
Record/replay functions are used for the reverse execution and deterministic
|
||||
replay of qemu execution. This implementation of deterministic replay can
|
||||
be used for deterministic debugging of guest code through a gdb remote
|
||||
interface.
|
||||
|
||||
Execution recording writes a non-deterministic events log, which can be later
|
||||
used for replaying the execution anywhere and for unlimited number of times.
|
||||
It also supports checkpointing for faster rewinding during reverse debugging.
|
||||
Execution replaying reads the log and replays all non-deterministic events
|
||||
including external input, hardware clocks, and interrupts.
|
||||
|
||||
Deterministic replay has the following features:
|
||||
* Deterministically replays whole system execution and all contents of
|
||||
the memory, state of the hardware devices, clocks, and screen of the VM.
|
||||
* Writes execution log into the file for later replaying for multiple times
|
||||
on different machines.
|
||||
* Supports i386, x86_64, and ARM hardware platforms.
|
||||
* Performs deterministic replay of all operations with keyboard and mouse
|
||||
input devices.
|
||||
|
||||
Usage of the record/replay:
|
||||
* First, record the execution, by adding the following arguments to the command line:
|
||||
'-icount shift=7,rr=record,rrfile=replay.bin -net none'.
|
||||
Block devices' images are not actually changed in the recording mode,
|
||||
because all of the changes are written to the temporary overlay file.
|
||||
* Then you can replay it by using another command
|
||||
line option: '-icount shift=7,rr=replay,rrfile=replay.bin -net none'
|
||||
* '-net none' option should also be specified if network replay patches
|
||||
are not applied.
|
||||
|
||||
Papers with description of deterministic replay implementation:
|
||||
http://www.computer.org/csdl/proceedings/csmr/2012/4666/00/4666a553-abs.html
|
||||
http://dl.acm.org/citation.cfm?id=2786805.2803179
|
||||
|
||||
Modifications of qemu include:
|
||||
* wrappers for clock and time functions to save their return values in the log
|
||||
* saving different asynchronous events (e.g. system shutdown) into the log
|
||||
* synchronization of the bottom halves execution
|
||||
* synchronization of the threads from thread pool
|
||||
* recording/replaying user input (mouse and keyboard)
|
||||
* adding internal checkpoints for cpu and io synchronization
|
||||
|
||||
Non-deterministic events
|
||||
------------------------
|
||||
|
||||
Our record/replay system is based on saving and replaying non-deterministic
|
||||
events (e.g. keyboard input) and simulating deterministic ones (e.g. reading
|
||||
from HDD or memory of the VM). Saving only non-deterministic events makes
|
||||
log file smaller, simulation faster, and allows using reverse debugging even
|
||||
for realtime applications.
|
||||
|
||||
The following non-deterministic data from peripheral devices is saved into
|
||||
the log: mouse and keyboard input, network packets, audio controller input,
|
||||
USB packets, serial port input, and hardware clocks (they are non-deterministic
|
||||
too, because their values are taken from the host machine). Inputs from
|
||||
simulated hardware, memory of VM, software interrupts, and execution of
|
||||
instructions are not saved into the log, because they are deterministic and
|
||||
can be replayed by simulating the behavior of virtual machine starting from
|
||||
initial state.
|
||||
|
||||
We had to solve three tasks to implement deterministic replay: recording
|
||||
non-deterministic events, replaying non-deterministic events, and checking
|
||||
that there is no divergence between record and replay modes.
|
||||
|
||||
We changed several parts of QEMU to make event log recording and replaying.
|
||||
Devices' models that have non-deterministic input from external devices were
|
||||
changed to write every external event into the execution log immediately.
|
||||
E.g. network packets are written into the log when they arrive into the virtual
|
||||
network adapter.
|
||||
|
||||
All non-deterministic events are coming from these devices. But to
|
||||
replay them we need to know at which moments they occur. We specify
|
||||
these moments by counting the number of instructions executed between
|
||||
every pair of consecutive events.
|
||||
|
||||
Instruction counting
|
||||
--------------------
|
||||
|
||||
QEMU should work in icount mode to use record/replay feature. icount was
|
||||
designed to allow deterministic execution in absence of external inputs
|
||||
of the virtual machine. We also use icount to control the occurrence of the
|
||||
non-deterministic events. The number of instructions elapsed from the last event
|
||||
is written to the log while recording the execution. In replay mode we
|
||||
can predict when to inject that event using the instruction counter.
|
||||
|
||||
Timers
|
||||
------
|
||||
|
||||
Timers are used to execute callbacks from different subsystems of QEMU
|
||||
at the specified moments of time. There are several kinds of timers:
|
||||
* Real time clock. Based on host time and used only for callbacks that
|
||||
do not change the virtual machine state. For this reason real time
|
||||
clock and timers does not affect deterministic replay at all.
|
||||
* Virtual clock. These timers run only during the emulation. In icount
|
||||
mode virtual clock value is calculated using executed instructions counter.
|
||||
That is why it is completely deterministic and does not have to be recorded.
|
||||
* Host clock. This clock is used by device models that simulate real time
|
||||
sources (e.g. real time clock chip). Host clock is the one of the sources
|
||||
of non-determinism. Host clock read operations should be logged to
|
||||
make the execution deterministic.
|
||||
* Real time clock for icount. This clock is similar to real time clock but
|
||||
it is used only for increasing virtual clock while virtual machine is
|
||||
sleeping. Due to its nature it is also non-deterministic as the host clock
|
||||
and has to be logged too.
|
||||
|
||||
Checkpoints
|
||||
-----------
|
||||
|
||||
Replaying of the execution of virtual machine is bound by sources of
|
||||
non-determinism. These are inputs from clock and peripheral devices,
|
||||
and QEMU thread scheduling. Thread scheduling affect on processing events
|
||||
from timers, asynchronous input-output, and bottom halves.
|
||||
|
||||
Invocations of timers are coupled with clock reads and changing the state
|
||||
of the virtual machine. Reads produce non-deterministic data taken from
|
||||
host clock. And VM state changes should preserve their order. Their relative
|
||||
order in replay mode must replicate the order of callbacks in record mode.
|
||||
To preserve this order we use checkpoints. When a specific clock is processed
|
||||
in record mode we save to the log special "checkpoint" event.
|
||||
Checkpoints here do not refer to virtual machine snapshots. They are just
|
||||
record/replay events used for synchronization.
|
||||
|
||||
QEMU in replay mode will try to invoke timers processing in random moment
|
||||
of time. That's why we do not process a group of timers until the checkpoint
|
||||
event will be read from the log. Such an event allows synchronizing CPU
|
||||
execution and timer events.
|
||||
|
||||
Another checkpoints application in record/replay is instruction counting
|
||||
while the virtual machine is idle. This function (qemu_clock_warp) is called
|
||||
from the wait loop. It changes virtual machine state and must be deterministic
|
||||
then. That is why we added checkpoint to this function to prevent its
|
||||
operation in replay mode when it does not correspond to record mode.
|
||||
|
||||
Bottom halves
|
||||
-------------
|
||||
|
||||
Disk I/O events are completely deterministic in our model, because
|
||||
in both record and replay modes we start virtual machine from the same
|
||||
disk state. But callbacks that virtual disk controller uses for reading and
|
||||
writing the disk may occur at different moments of time in record and replay
|
||||
modes.
|
||||
|
||||
Reading and writing requests are created by CPU thread of QEMU. Later these
|
||||
requests proceed to block layer which creates "bottom halves". Bottom
|
||||
halves consist of callback and its parameters. They are processed when
|
||||
main loop locks the global mutex. These locks are not synchronized with
|
||||
replaying process because main loop also processes the events that do not
|
||||
affect the virtual machine state (like user interaction with monitor).
|
||||
|
||||
That is why we had to implement saving and replaying bottom halves callbacks
|
||||
synchronously to the CPU execution. When the callback is about to execute
|
||||
it is added to the queue in the replay module. This queue is written to the
|
||||
log when its callbacks are executed. In replay mode callbacks are not processed
|
||||
until the corresponding event is read from the events log file.
|
||||
|
||||
Sometimes the block layer uses asynchronous callbacks for its internal purposes
|
||||
(like reading or writing VM snapshots or disk image cluster tables). In this
|
||||
case bottom halves are not marked as "replayable" and do not saved
|
||||
into the log.
|
2
exec.c
2
exec.c
@ -50,6 +50,7 @@
|
||||
#include "qemu/rcu_queue.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "translate-all.h"
|
||||
#include "sysemu/replay.h"
|
||||
|
||||
#include "exec/memory-internal.h"
|
||||
#include "exec/ram_addr.h"
|
||||
@ -882,6 +883,7 @@ void cpu_abort(CPUState *cpu, const char *fmt, ...)
|
||||
}
|
||||
va_end(ap2);
|
||||
va_end(ap);
|
||||
replay_finish();
|
||||
#if defined(CONFIG_USER_ONLY)
|
||||
{
|
||||
struct sigaction act;
|
||||
|
@ -23,6 +23,8 @@
|
||||
#include "hw/usb.h"
|
||||
#include "sysemu/bt.h"
|
||||
#include "hw/bt.h"
|
||||
#include "qapi/qmp/qerror.h"
|
||||
#include "sysemu/replay.h"
|
||||
|
||||
struct bt_hci_s {
|
||||
uint8_t *(*evt_packet)(void *opaque);
|
||||
@ -72,6 +74,8 @@ struct bt_hci_s {
|
||||
|
||||
struct HCIInfo info;
|
||||
struct bt_device_s device;
|
||||
|
||||
Error *replay_blocker;
|
||||
};
|
||||
|
||||
#define DEFAULT_RSSI_DBM 20
|
||||
@ -2189,6 +2193,9 @@ struct HCIInfo *bt_new_hci(struct bt_scatternet_s *net)
|
||||
|
||||
s->device.handle_destroy = bt_hci_destroy;
|
||||
|
||||
error_setg(&s->replay_blocker, QERR_REPLAY_NOT_SUPPORTED, "-bt hci");
|
||||
replay_add_blocker(s->replay_blocker);
|
||||
|
||||
return &s->info;
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "qemu/timer.h"
|
||||
#include "hw/ptimer.h"
|
||||
#include "qemu/host-utils.h"
|
||||
#include "sysemu/replay.h"
|
||||
|
||||
struct ptimer_state
|
||||
{
|
||||
@ -27,7 +28,7 @@ struct ptimer_state
|
||||
static void ptimer_trigger(ptimer_state *s)
|
||||
{
|
||||
if (s->bh) {
|
||||
qemu_bh_schedule(s->bh);
|
||||
replay_bh_schedule_event(s->bh);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,6 +208,11 @@ void aio_notify(AioContext *ctx);
|
||||
*/
|
||||
void aio_notify_accept(AioContext *ctx);
|
||||
|
||||
/**
|
||||
* aio_bh_call: Executes callback function of the specified BH.
|
||||
*/
|
||||
void aio_bh_call(QEMUBH *bh);
|
||||
|
||||
/**
|
||||
* aio_bh_poll: Poll bottom halves for an AioContext.
|
||||
*
|
||||
|
@ -190,6 +190,7 @@ struct TranslationBlock {
|
||||
#define CF_LAST_IO 0x8000 /* Last insn may be an IO access. */
|
||||
#define CF_NOCACHE 0x10000 /* To be freed after execution */
|
||||
#define CF_USE_ICOUNT 0x20000
|
||||
#define CF_IGNORE_ICOUNT 0x40000 /* Do not generate icount code */
|
||||
|
||||
void *tc_ptr; /* pointer to the translated code */
|
||||
uint8_t *tc_search; /* pointer to search data */
|
||||
|
@ -106,4 +106,7 @@
|
||||
#define QERR_UNSUPPORTED \
|
||||
"this feature or command is not currently supported"
|
||||
|
||||
#define QERR_REPLAY_NOT_SUPPORTED \
|
||||
"Record/replay feature is not supported for '%s'"
|
||||
|
||||
#endif /* QERROR_H */
|
||||
|
120
include/sysemu/replay.h
Normal file
120
include/sysemu/replay.h
Normal file
@ -0,0 +1,120 @@
|
||||
#ifndef REPLAY_H
|
||||
#define REPLAY_H
|
||||
|
||||
/*
|
||||
* replay.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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "qapi-types.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/typedefs.h"
|
||||
|
||||
/* replay clock kinds */
|
||||
enum ReplayClockKind {
|
||||
/* host_clock */
|
||||
REPLAY_CLOCK_HOST,
|
||||
/* virtual_rt_clock */
|
||||
REPLAY_CLOCK_VIRTUAL_RT,
|
||||
REPLAY_CLOCK_COUNT
|
||||
};
|
||||
typedef enum ReplayClockKind ReplayClockKind;
|
||||
|
||||
/* IDs of the checkpoints */
|
||||
enum ReplayCheckpoint {
|
||||
CHECKPOINT_CLOCK_WARP,
|
||||
CHECKPOINT_RESET_REQUESTED,
|
||||
CHECKPOINT_SUSPEND_REQUESTED,
|
||||
CHECKPOINT_CLOCK_VIRTUAL,
|
||||
CHECKPOINT_CLOCK_HOST,
|
||||
CHECKPOINT_CLOCK_VIRTUAL_RT,
|
||||
CHECKPOINT_INIT,
|
||||
CHECKPOINT_RESET,
|
||||
CHECKPOINT_COUNT
|
||||
};
|
||||
typedef enum ReplayCheckpoint ReplayCheckpoint;
|
||||
|
||||
extern ReplayMode replay_mode;
|
||||
|
||||
/* Replay process control functions */
|
||||
|
||||
/*! Enables recording or saving event log with specified parameters */
|
||||
void replay_configure(struct QemuOpts *opts);
|
||||
/*! Initializes timers used for snapshotting and enables events recording */
|
||||
void replay_start(void);
|
||||
/*! Closes replay log file and frees other resources. */
|
||||
void replay_finish(void);
|
||||
/*! Adds replay blocker with the specified error description */
|
||||
void replay_add_blocker(Error *reason);
|
||||
|
||||
/* Processing the instructions */
|
||||
|
||||
/*! Returns number of executed instructions. */
|
||||
uint64_t replay_get_current_step(void);
|
||||
/*! Returns number of instructions to execute in replay mode. */
|
||||
int replay_get_instructions(void);
|
||||
/*! Updates instructions counter in replay mode. */
|
||||
void replay_account_executed_instructions(void);
|
||||
|
||||
/* Interrupts and exceptions */
|
||||
|
||||
/*! Called by exception handler to write or read
|
||||
exception processing events. */
|
||||
bool replay_exception(void);
|
||||
/*! Used to determine that exception is pending.
|
||||
Does not proceed to the next event in the log. */
|
||||
bool replay_has_exception(void);
|
||||
/*! Called by interrupt handlers to write or read
|
||||
interrupt processing events.
|
||||
\return true if interrupt should be processed */
|
||||
bool replay_interrupt(void);
|
||||
/*! Tries to read interrupt event from the file.
|
||||
Returns true, when interrupt request is pending */
|
||||
bool replay_has_interrupt(void);
|
||||
|
||||
/* Processing clocks and other time sources */
|
||||
|
||||
/*! Save the specified clock */
|
||||
int64_t replay_save_clock(ReplayClockKind kind, int64_t clock);
|
||||
/*! Read the specified clock from the log or return cached data */
|
||||
int64_t replay_read_clock(ReplayClockKind kind);
|
||||
/*! Saves or reads the clock depending on the current replay mode. */
|
||||
#define REPLAY_CLOCK(clock, value) \
|
||||
(replay_mode == REPLAY_MODE_PLAY ? replay_read_clock((clock)) \
|
||||
: replay_mode == REPLAY_MODE_RECORD \
|
||||
? replay_save_clock((clock), (value)) \
|
||||
: (value))
|
||||
|
||||
/* Events */
|
||||
|
||||
/*! Called when qemu shutdown is requested. */
|
||||
void replay_shutdown_request(void);
|
||||
/*! Should be called at check points in the execution.
|
||||
These check points are skipped, if they were not met.
|
||||
Saves checkpoint in the SAVE mode and validates in the PLAY mode.
|
||||
Returns 0 in PLAY mode if checkpoint was not found.
|
||||
Returns 1 in all other cases. */
|
||||
bool replay_checkpoint(ReplayCheckpoint checkpoint);
|
||||
|
||||
/* Asynchronous events queue */
|
||||
|
||||
/*! Disables storing events in the queue */
|
||||
void replay_disable_events(void);
|
||||
/*! Returns true when saving events is enabled */
|
||||
bool replay_events_enabled(void);
|
||||
/*! Adds bottom half event to the queue */
|
||||
void replay_bh_schedule_event(QEMUBH *bh);
|
||||
/*! Adds input event to the queue */
|
||||
void replay_input_event(QemuConsole *src, InputEvent *evt);
|
||||
/*! Adds input sync event to the queue */
|
||||
void replay_input_sync_event(void);
|
||||
|
||||
#endif
|
@ -33,7 +33,9 @@ void qemu_input_handler_bind(QemuInputHandlerState *s,
|
||||
const char *device_id, int head,
|
||||
Error **errp);
|
||||
void qemu_input_event_send(QemuConsole *src, InputEvent *evt);
|
||||
void qemu_input_event_send_impl(QemuConsole *src, InputEvent *evt);
|
||||
void qemu_input_event_sync(void);
|
||||
void qemu_input_event_sync_impl(void);
|
||||
|
||||
InputEvent *qemu_input_event_new_key(KeyValue *key, bool down);
|
||||
void qemu_input_event_send_key(QemuConsole *src, KeyValue *key, bool down);
|
||||
|
@ -506,6 +506,9 @@ int main_loop_wait(int nonblocking)
|
||||
slirp_pollfds_poll(gpollfds, (ret < 0));
|
||||
#endif
|
||||
|
||||
/* CPU thread can infinitely wait for event after
|
||||
missing the warp */
|
||||
qemu_clock_warp(QEMU_CLOCK_VIRTUAL);
|
||||
qemu_clock_run_all_timers();
|
||||
|
||||
return ret;
|
||||
|
@ -3876,3 +3876,21 @@
|
||||
|
||||
# Rocker ethernet network switch
|
||||
{ 'include': 'qapi/rocker.json' }
|
||||
|
||||
##
|
||||
# ReplayMode:
|
||||
#
|
||||
# Mode of the replay subsystem.
|
||||
#
|
||||
# @none: normal execution mode. Replay or record are not enabled.
|
||||
#
|
||||
# @record: record mode. All non-deterministic data is written into the
|
||||
# replay log.
|
||||
#
|
||||
# @play: replay mode. Non-deterministic data required for system execution
|
||||
# is read from the log.
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'enum': 'ReplayMode',
|
||||
'data': [ 'none', 'record', 'play' ] }
|
||||
|
@ -3157,12 +3157,12 @@ re-inject them.
|
||||
ETEXI
|
||||
|
||||
DEF("icount", HAS_ARG, QEMU_OPTION_icount, \
|
||||
"-icount [shift=N|auto][,align=on|off][,sleep=no]\n" \
|
||||
"-icount [shift=N|auto][,align=on|off][,sleep=no,rr=record|replay,rrfile=<filename>]\n" \
|
||||
" enable virtual instruction counter with 2^N clock ticks per\n" \
|
||||
" instruction, enable aligning the host and virtual clocks\n" \
|
||||
" or disable real time cpu sleeping\n", QEMU_ARCH_ALL)
|
||||
STEXI
|
||||
@item -icount [shift=@var{N}|auto]
|
||||
@item -icount [shift=@var{N}|auto][,rr=record|replay,rrfile=@var{filename}]
|
||||
@findex -icount
|
||||
Enable virtual instruction counter. The virtual cpu will execute one
|
||||
instruction every 2^@var{N} ns of virtual time. If @code{auto} is specified
|
||||
@ -3191,6 +3191,10 @@ Currently this option does not work when @option{shift} is @code{auto}.
|
||||
Note: The sync algorithm will work for those shift values for which
|
||||
the guest clock runs ahead of the host clock. Typically this happens
|
||||
when the shift value is high (how high depends on the host machine).
|
||||
|
||||
When @option{rr} option is specified deterministic record/replay is enabled.
|
||||
Replay log is written into @var{filename} file in record mode and
|
||||
read from this file in replay mode.
|
||||
ETEXI
|
||||
|
||||
DEF("watchdog", HAS_ARG, QEMU_OPTION_watchdog, \
|
||||
|
43
qemu-timer.c
43
qemu-timer.c
@ -24,6 +24,8 @@
|
||||
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "sysemu/replay.h"
|
||||
#include "sysemu/sysemu.h"
|
||||
|
||||
#ifdef CONFIG_POSIX
|
||||
#include <pthread.h>
|
||||
@ -477,10 +479,31 @@ bool timerlist_run_timers(QEMUTimerList *timer_list)
|
||||
void *opaque;
|
||||
|
||||
qemu_event_reset(&timer_list->timers_done_ev);
|
||||
if (!timer_list->clock->enabled) {
|
||||
if (!timer_list->clock->enabled || !timer_list->active_timers) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (timer_list->clock->type) {
|
||||
case QEMU_CLOCK_REALTIME:
|
||||
break;
|
||||
default:
|
||||
case QEMU_CLOCK_VIRTUAL:
|
||||
if (!replay_checkpoint(CHECKPOINT_CLOCK_VIRTUAL)) {
|
||||
goto out;
|
||||
}
|
||||
break;
|
||||
case QEMU_CLOCK_HOST:
|
||||
if (!replay_checkpoint(CHECKPOINT_CLOCK_HOST)) {
|
||||
goto out;
|
||||
}
|
||||
break;
|
||||
case QEMU_CLOCK_VIRTUAL_RT:
|
||||
if (!replay_checkpoint(CHECKPOINT_CLOCK_VIRTUAL_RT)) {
|
||||
goto out;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
current_time = qemu_clock_get_ns(timer_list->clock->type);
|
||||
for(;;) {
|
||||
qemu_mutex_lock(&timer_list->active_timers_lock);
|
||||
@ -544,11 +567,17 @@ int64_t timerlistgroup_deadline_ns(QEMUTimerListGroup *tlg)
|
||||
{
|
||||
int64_t deadline = -1;
|
||||
QEMUClockType type;
|
||||
bool play = replay_mode == REPLAY_MODE_PLAY;
|
||||
for (type = 0; type < QEMU_CLOCK_MAX; type++) {
|
||||
if (qemu_clock_use_for_deadline(tlg->tl[type]->clock->type)) {
|
||||
deadline = qemu_soonest_timeout(deadline,
|
||||
timerlist_deadline_ns(
|
||||
tlg->tl[type]));
|
||||
if (qemu_clock_use_for_deadline(type)) {
|
||||
if (!play || type == QEMU_CLOCK_REALTIME) {
|
||||
deadline = qemu_soonest_timeout(deadline,
|
||||
timerlist_deadline_ns(tlg->tl[type]));
|
||||
} else {
|
||||
/* Read clock from the replay file and
|
||||
do not calculate the deadline, based on virtual clock. */
|
||||
qemu_clock_get_ns(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
return deadline;
|
||||
@ -570,7 +599,7 @@ int64_t qemu_clock_get_ns(QEMUClockType type)
|
||||
return cpu_get_clock();
|
||||
}
|
||||
case QEMU_CLOCK_HOST:
|
||||
now = get_clock_realtime();
|
||||
now = REPLAY_CLOCK(REPLAY_CLOCK_HOST, get_clock_realtime());
|
||||
last = clock->last;
|
||||
clock->last = now;
|
||||
if (now < last || now > (last + get_max_clock_jump())) {
|
||||
@ -578,7 +607,7 @@ int64_t qemu_clock_get_ns(QEMUClockType type)
|
||||
}
|
||||
return now;
|
||||
case QEMU_CLOCK_VIRTUAL_RT:
|
||||
return cpu_get_clock();
|
||||
return REPLAY_CLOCK(REPLAY_CLOCK_VIRTUAL_RT, cpu_get_clock());
|
||||
}
|
||||
}
|
||||
|
||||
|
5
replay/Makefile.objs
Normal file
5
replay/Makefile.objs
Normal file
@ -0,0 +1,5 @@
|
||||
common-obj-y += replay.o
|
||||
common-obj-y += replay-internal.o
|
||||
common-obj-y += replay-events.o
|
||||
common-obj-y += replay-time.o
|
||||
common-obj-y += replay-input.o
|
279
replay/replay-events.c
Normal file
279
replay/replay-events.c
Normal file
@ -0,0 +1,279 @@
|
||||
/*
|
||||
* 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-common.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "sysemu/replay.h"
|
||||
#include "replay-internal.h"
|
||||
#include "block/aio.h"
|
||||
#include "ui/input.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 unsigned int read_event_kind = -1;
|
||||
static uint64_t read_id = -1;
|
||||
static int read_checkpoint = -1;
|
||||
|
||||
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_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;
|
||||
default:
|
||||
error_report("Replay: invalid async event ID (%d) in the queue",
|
||||
event->event_kind);
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void replay_enable_events(void)
|
||||
{
|
||||
events_enabled = true;
|
||||
}
|
||||
|
||||
bool replay_has_events(void)
|
||||
{
|
||||
return !QTAILQ_EMPTY(&events_list);
|
||||
}
|
||||
|
||||
void replay_flush_events(void)
|
||||
{
|
||||
replay_mutex_lock();
|
||||
while (!QTAILQ_EMPTY(&events_list)) {
|
||||
Event *event = QTAILQ_FIRST(&events_list);
|
||||
replay_mutex_unlock();
|
||||
replay_run_event(event);
|
||||
replay_mutex_lock();
|
||||
QTAILQ_REMOVE(&events_list, event, events);
|
||||
g_free(event);
|
||||
}
|
||||
replay_mutex_unlock();
|
||||
}
|
||||
|
||||
void replay_disable_events(void)
|
||||
{
|
||||
if (replay_mode != REPLAY_MODE_NONE) {
|
||||
events_enabled = false;
|
||||
/* Flush events queue before waiting of completion */
|
||||
replay_flush_events();
|
||||
}
|
||||
}
|
||||
|
||||
void replay_clear_events(void)
|
||||
{
|
||||
replay_mutex_lock();
|
||||
while (!QTAILQ_EMPTY(&events_list)) {
|
||||
Event *event = QTAILQ_FIRST(&events_list);
|
||||
QTAILQ_REMOVE(&events_list, event, events);
|
||||
|
||||
g_free(event);
|
||||
}
|
||||
replay_mutex_unlock();
|
||||
}
|
||||
|
||||
/*! Adds specified async event to the queue */
|
||||
static 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;
|
||||
|
||||
replay_mutex_lock();
|
||||
QTAILQ_INSERT_TAIL(&events_list, event, events);
|
||||
replay_mutex_unlock();
|
||||
}
|
||||
|
||||
void replay_bh_schedule_event(QEMUBH *bh)
|
||||
{
|
||||
if (replay_mode != REPLAY_MODE_NONE) {
|
||||
uint64_t id = replay_get_current_step();
|
||||
replay_add_event(REPLAY_ASYNC_EVENT_BH, bh, NULL, id);
|
||||
} else {
|
||||
qemu_bh_schedule(bh);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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:
|
||||
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;
|
||||
default:
|
||||
error_report("Unknown ID %d of replay event", read_event_kind);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Called with replay mutex locked */
|
||||
void replay_save_events(int checkpoint)
|
||||
{
|
||||
while (!QTAILQ_EMPTY(&events_list)) {
|
||||
Event *event = QTAILQ_FIRST(&events_list);
|
||||
replay_save_event(event, checkpoint);
|
||||
|
||||
replay_mutex_unlock();
|
||||
replay_run_event(event);
|
||||
replay_mutex_lock();
|
||||
QTAILQ_REMOVE(&events_list, event, events);
|
||||
g_free(event);
|
||||
}
|
||||
}
|
||||
|
||||
static Event *replay_read_event(int checkpoint)
|
||||
{
|
||||
Event *event;
|
||||
if (read_event_kind == -1) {
|
||||
read_checkpoint = replay_get_byte();
|
||||
read_event_kind = replay_get_byte();
|
||||
read_id = -1;
|
||||
replay_check_error();
|
||||
}
|
||||
|
||||
if (checkpoint != read_checkpoint) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Events that has not to be in the queue */
|
||||
switch (read_event_kind) {
|
||||
case REPLAY_ASYNC_EVENT_BH:
|
||||
if (read_id == -1) {
|
||||
read_id = replay_get_qword();
|
||||
}
|
||||
break;
|
||||
case REPLAY_ASYNC_EVENT_INPUT:
|
||||
event = g_malloc0(sizeof(Event));
|
||||
event->event_kind = 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 = read_event_kind;
|
||||
event->opaque = 0;
|
||||
return event;
|
||||
default:
|
||||
error_report("Unknown ID %d of replay event", read_event_kind);
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
|
||||
QTAILQ_FOREACH(event, &events_list, events) {
|
||||
if (event->event_kind == read_event_kind
|
||||
&& (read_id == -1 || read_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)
|
||||
{
|
||||
while (replay_data_kind == EVENT_ASYNC) {
|
||||
Event *event = replay_read_event(checkpoint);
|
||||
if (!event) {
|
||||
break;
|
||||
}
|
||||
replay_mutex_unlock();
|
||||
replay_run_event(event);
|
||||
replay_mutex_lock();
|
||||
|
||||
g_free(event);
|
||||
replay_finish_event();
|
||||
read_event_kind = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void replay_init_events(void)
|
||||
{
|
||||
read_event_kind = -1;
|
||||
}
|
||||
|
||||
void replay_finish_events(void)
|
||||
{
|
||||
events_enabled = false;
|
||||
replay_clear_events();
|
||||
}
|
||||
|
||||
bool replay_events_enabled(void)
|
||||
{
|
||||
return events_enabled;
|
||||
}
|
160
replay/replay-input.c
Normal file
160
replay/replay-input.c
Normal file
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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-common.h"
|
||||
#include "sysemu/replay.h"
|
||||
#include "replay-internal.h"
|
||||
#include "qemu/notify.h"
|
||||
#include "ui/input.h"
|
||||
#include "qapi/qmp-output-visitor.h"
|
||||
#include "qapi/qmp-input-visitor.h"
|
||||
#include "qapi-visit.h"
|
||||
|
||||
static InputEvent *qapi_clone_InputEvent(InputEvent *src)
|
||||
{
|
||||
QmpOutputVisitor *qov;
|
||||
QmpInputVisitor *qiv;
|
||||
Visitor *ov, *iv;
|
||||
QObject *obj;
|
||||
InputEvent *dst = NULL;
|
||||
|
||||
qov = qmp_output_visitor_new();
|
||||
ov = qmp_output_get_visitor(qov);
|
||||
visit_type_InputEvent(ov, &src, NULL, &error_abort);
|
||||
obj = qmp_output_get_qobject(qov);
|
||||
qmp_output_visitor_cleanup(qov);
|
||||
if (!obj) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
qiv = qmp_input_visitor_new(obj);
|
||||
iv = qmp_input_get_visitor(qiv);
|
||||
visit_type_InputEvent(iv, &dst, NULL, &error_abort);
|
||||
qmp_input_visitor_cleanup(qiv);
|
||||
qobject_decref(obj);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
void replay_save_input_event(InputEvent *evt)
|
||||
{
|
||||
replay_put_dword(evt->type);
|
||||
|
||||
switch (evt->type) {
|
||||
case INPUT_EVENT_KIND_KEY:
|
||||
replay_put_dword(evt->u.key->key->type);
|
||||
|
||||
switch (evt->u.key->key->type) {
|
||||
case KEY_VALUE_KIND_NUMBER:
|
||||
replay_put_qword(evt->u.key->key->u.number);
|
||||
replay_put_byte(evt->u.key->down);
|
||||
break;
|
||||
case KEY_VALUE_KIND_QCODE:
|
||||
replay_put_dword(evt->u.key->key->u.qcode);
|
||||
replay_put_byte(evt->u.key->down);
|
||||
break;
|
||||
case KEY_VALUE_KIND_MAX:
|
||||
/* keep gcc happy */
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case INPUT_EVENT_KIND_BTN:
|
||||
replay_put_dword(evt->u.btn->button);
|
||||
replay_put_byte(evt->u.btn->down);
|
||||
break;
|
||||
case INPUT_EVENT_KIND_REL:
|
||||
replay_put_dword(evt->u.rel->axis);
|
||||
replay_put_qword(evt->u.rel->value);
|
||||
break;
|
||||
case INPUT_EVENT_KIND_ABS:
|
||||
replay_put_dword(evt->u.abs->axis);
|
||||
replay_put_qword(evt->u.abs->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 = &key;
|
||||
evt.u.key->key->type = replay_get_dword();
|
||||
|
||||
switch (evt.u.key->key->type) {
|
||||
case KEY_VALUE_KIND_NUMBER:
|
||||
evt.u.key->key->u.number = replay_get_qword();
|
||||
evt.u.key->down = replay_get_byte();
|
||||
break;
|
||||
case KEY_VALUE_KIND_QCODE:
|
||||
evt.u.key->key->u.qcode = (QKeyCode)replay_get_dword();
|
||||
evt.u.key->down = replay_get_byte();
|
||||
break;
|
||||
case KEY_VALUE_KIND_MAX:
|
||||
/* keep gcc happy */
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case INPUT_EVENT_KIND_BTN:
|
||||
evt.u.btn = &btn;
|
||||
evt.u.btn->button = (InputButton)replay_get_dword();
|
||||
evt.u.btn->down = replay_get_byte();
|
||||
break;
|
||||
case INPUT_EVENT_KIND_REL:
|
||||
evt.u.rel = &rel;
|
||||
evt.u.rel->axis = (InputAxis)replay_get_dword();
|
||||
evt.u.rel->value = replay_get_qword();
|
||||
break;
|
||||
case INPUT_EVENT_KIND_ABS:
|
||||
evt.u.abs = &abs;
|
||||
evt.u.abs->axis = (InputAxis)replay_get_dword();
|
||||
evt.u.abs->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();
|
||||
}
|
||||
}
|
206
replay/replay-internal.c
Normal file
206
replay/replay-internal.c
Normal file
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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-common.h"
|
||||
#include "sysemu/replay.h"
|
||||
#include "replay-internal.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "sysemu/sysemu.h"
|
||||
|
||||
unsigned int replay_data_kind = -1;
|
||||
static unsigned int replay_has_unread_data;
|
||||
|
||||
/* Mutex to protect reading and writing events to the log.
|
||||
replay_data_kind and replay_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;
|
||||
|
||||
/* File for replay writing */
|
||||
FILE *replay_file;
|
||||
|
||||
void replay_put_byte(uint8_t byte)
|
||||
{
|
||||
if (replay_file) {
|
||||
putc(byte, replay_file);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
fwrite(buf, 1, size, replay_file);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t replay_get_byte(void)
|
||||
{
|
||||
uint8_t byte = 0;
|
||||
if (replay_file) {
|
||||
byte = getc(replay_file);
|
||||
}
|
||||
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) {
|
||||
error_report("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) {
|
||||
error_report("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_has_unread_data) {
|
||||
replay_data_kind = replay_get_byte();
|
||||
if (replay_data_kind == EVENT_INSTRUCTION) {
|
||||
replay_state.instructions_count = replay_get_dword();
|
||||
}
|
||||
replay_check_error();
|
||||
replay_has_unread_data = 1;
|
||||
if (replay_data_kind >= EVENT_COUNT) {
|
||||
error_report("Replay: unknown event kind %d", replay_data_kind);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void replay_finish_event(void)
|
||||
{
|
||||
replay_has_unread_data = 0;
|
||||
replay_fetch_data_kind();
|
||||
}
|
||||
|
||||
void replay_mutex_init(void)
|
||||
{
|
||||
qemu_mutex_init(&lock);
|
||||
}
|
||||
|
||||
void replay_mutex_destroy(void)
|
||||
{
|
||||
qemu_mutex_destroy(&lock);
|
||||
}
|
||||
|
||||
void replay_mutex_lock(void)
|
||||
{
|
||||
qemu_mutex_lock(&lock);
|
||||
}
|
||||
|
||||
void replay_mutex_unlock(void)
|
||||
{
|
||||
qemu_mutex_unlock(&lock);
|
||||
}
|
||||
|
||||
/*! Saves cached instructions. */
|
||||
void replay_save_instructions(void)
|
||||
{
|
||||
if (replay_file && replay_mode == REPLAY_MODE_RECORD) {
|
||||
replay_mutex_lock();
|
||||
int diff = (int)(replay_get_current_step() - replay_state.current_step);
|
||||
if (diff > 0) {
|
||||
replay_put_event(EVENT_INSTRUCTION);
|
||||
replay_put_dword(diff);
|
||||
replay_state.current_step += diff;
|
||||
}
|
||||
replay_mutex_unlock();
|
||||
}
|
||||
}
|
140
replay/replay-internal.h
Normal file
140
replay/replay-internal.h
Normal file
@ -0,0 +1,140 @@
|
||||
#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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
enum ReplayEvents {
|
||||
/* for instruction event */
|
||||
EVENT_INSTRUCTION,
|
||||
/* for software interrupt */
|
||||
EVENT_INTERRUPT,
|
||||
/* for emulated exceptions */
|
||||
EVENT_EXCEPTION,
|
||||
/* for async events */
|
||||
EVENT_ASYNC,
|
||||
/* for shutdown request */
|
||||
EVENT_SHUTDOWN,
|
||||
/* 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_INPUT,
|
||||
REPLAY_ASYNC_EVENT_INPUT_SYNC,
|
||||
REPLAY_ASYNC_COUNT
|
||||
};
|
||||
|
||||
typedef enum ReplayAsyncEventKind ReplayAsyncEventKind;
|
||||
|
||||
typedef struct ReplayState {
|
||||
/*! Cached clock values. */
|
||||
int64_t cached_clock[REPLAY_CLOCK_COUNT];
|
||||
/*! Current step - number of processed instructions and timer events. */
|
||||
uint64_t current_step;
|
||||
/*! Number of instructions to be executed before other events happen. */
|
||||
int instructions_count;
|
||||
} ReplayState;
|
||||
extern ReplayState replay_state;
|
||||
|
||||
extern unsigned int replay_data_kind;
|
||||
|
||||
/* File for replay writing */
|
||||
extern FILE *replay_file;
|
||||
|
||||
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 */
|
||||
|
||||
void replay_mutex_init(void);
|
||||
void replay_mutex_destroy(void);
|
||||
void replay_mutex_lock(void);
|
||||
void replay_mutex_unlock(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
|
||||
replay_data_kind variable. */
|
||||
void replay_fetch_data_kind(void);
|
||||
|
||||
/*! 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);
|
||||
/*! Enables storing events in the queue */
|
||||
void replay_enable_events(void);
|
||||
/*! Flushes events queue */
|
||||
void replay_flush_events(void);
|
||||
/*! Clears events list before loading new VM state */
|
||||
void replay_clear_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);
|
||||
|
||||
/* 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);
|
||||
|
||||
#endif
|
64
replay/replay-time.c
Normal file
64
replay/replay-time.c
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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-common.h"
|
||||
#include "sysemu/replay.h"
|
||||
#include "replay-internal.h"
|
||||
#include "qemu/error-report.h"
|
||||
|
||||
int64_t replay_save_clock(ReplayClockKind kind, int64_t clock)
|
||||
{
|
||||
replay_save_instructions();
|
||||
|
||||
if (replay_file) {
|
||||
replay_mutex_lock();
|
||||
replay_put_event(EVENT_CLOCK + kind);
|
||||
replay_put_qword(clock);
|
||||
replay_mutex_unlock();
|
||||
}
|
||||
|
||||
return clock;
|
||||
}
|
||||
|
||||
void replay_read_next_clock(ReplayClockKind kind)
|
||||
{
|
||||
unsigned int read_kind = replay_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)
|
||||
{
|
||||
replay_account_executed_instructions();
|
||||
|
||||
if (replay_file) {
|
||||
int64_t ret;
|
||||
replay_mutex_lock();
|
||||
if (replay_next_event_is(EVENT_CLOCK + kind)) {
|
||||
replay_read_next_clock(kind);
|
||||
}
|
||||
ret = replay_state.cached_clock[kind];
|
||||
replay_mutex_unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
error_report("REPLAY INTERNAL ERROR %d", __LINE__);
|
||||
exit(1);
|
||||
}
|
342
replay/replay.c
Normal file
342
replay/replay.c
Normal file
@ -0,0 +1,342 @@
|
||||
/*
|
||||
* 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-common.h"
|
||||
#include "sysemu/replay.h"
|
||||
#include "replay-internal.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "sysemu/sysemu.h"
|
||||
#include "qemu/error-report.h"
|
||||
|
||||
/* Current version of the replay mechanism.
|
||||
Increase it when file format changes. */
|
||||
#define REPLAY_VERSION 0xe02002
|
||||
/* Size of replay log header */
|
||||
#define HEADER_SIZE (sizeof(uint32_t) + sizeof(uint64_t))
|
||||
|
||||
ReplayMode replay_mode = REPLAY_MODE_NONE;
|
||||
|
||||
/* Name of replay file */
|
||||
static char *replay_filename;
|
||||
ReplayState replay_state;
|
||||
static GSList *replay_blockers;
|
||||
|
||||
bool replay_next_event_is(int event)
|
||||
{
|
||||
bool res = false;
|
||||
|
||||
/* nothing to skip - not all instructions used */
|
||||
if (replay_state.instructions_count != 0) {
|
||||
assert(replay_data_kind == EVENT_INSTRUCTION);
|
||||
return event == EVENT_INSTRUCTION;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (event == replay_data_kind) {
|
||||
res = true;
|
||||
}
|
||||
switch (replay_data_kind) {
|
||||
case EVENT_SHUTDOWN:
|
||||
replay_finish_event();
|
||||
qemu_system_shutdown_request();
|
||||
break;
|
||||
default:
|
||||
/* clock, time_t, checkpoint and other events */
|
||||
return res;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
uint64_t replay_get_current_step(void)
|
||||
{
|
||||
return cpu_get_icount_raw();
|
||||
}
|
||||
|
||||
int replay_get_instructions(void)
|
||||
{
|
||||
int res = 0;
|
||||
replay_mutex_lock();
|
||||
if (replay_next_event_is(EVENT_INSTRUCTION)) {
|
||||
res = replay_state.instructions_count;
|
||||
}
|
||||
replay_mutex_unlock();
|
||||
return res;
|
||||
}
|
||||
|
||||
void replay_account_executed_instructions(void)
|
||||
{
|
||||
if (replay_mode == REPLAY_MODE_PLAY) {
|
||||
replay_mutex_lock();
|
||||
if (replay_state.instructions_count > 0) {
|
||||
int count = (int)(replay_get_current_step()
|
||||
- replay_state.current_step);
|
||||
replay_state.instructions_count -= count;
|
||||
replay_state.current_step += count;
|
||||
if (replay_state.instructions_count == 0) {
|
||||
assert(replay_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();
|
||||
}
|
||||
}
|
||||
replay_mutex_unlock();
|
||||
}
|
||||
}
|
||||
|
||||
bool replay_exception(void)
|
||||
{
|
||||
if (replay_mode == REPLAY_MODE_RECORD) {
|
||||
replay_save_instructions();
|
||||
replay_mutex_lock();
|
||||
replay_put_event(EVENT_EXCEPTION);
|
||||
replay_mutex_unlock();
|
||||
return true;
|
||||
} else if (replay_mode == REPLAY_MODE_PLAY) {
|
||||
bool res = replay_has_exception();
|
||||
if (res) {
|
||||
replay_mutex_lock();
|
||||
replay_finish_event();
|
||||
replay_mutex_unlock();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool replay_has_exception(void)
|
||||
{
|
||||
bool res = false;
|
||||
if (replay_mode == REPLAY_MODE_PLAY) {
|
||||
replay_account_executed_instructions();
|
||||
replay_mutex_lock();
|
||||
res = replay_next_event_is(EVENT_EXCEPTION);
|
||||
replay_mutex_unlock();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool replay_interrupt(void)
|
||||
{
|
||||
if (replay_mode == REPLAY_MODE_RECORD) {
|
||||
replay_save_instructions();
|
||||
replay_mutex_lock();
|
||||
replay_put_event(EVENT_INTERRUPT);
|
||||
replay_mutex_unlock();
|
||||
return true;
|
||||
} else if (replay_mode == REPLAY_MODE_PLAY) {
|
||||
bool res = replay_has_interrupt();
|
||||
if (res) {
|
||||
replay_mutex_lock();
|
||||
replay_finish_event();
|
||||
replay_mutex_unlock();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool replay_has_interrupt(void)
|
||||
{
|
||||
bool res = false;
|
||||
if (replay_mode == REPLAY_MODE_PLAY) {
|
||||
replay_account_executed_instructions();
|
||||
replay_mutex_lock();
|
||||
res = replay_next_event_is(EVENT_INTERRUPT);
|
||||
replay_mutex_unlock();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void replay_shutdown_request(void)
|
||||
{
|
||||
if (replay_mode == REPLAY_MODE_RECORD) {
|
||||
replay_mutex_lock();
|
||||
replay_put_event(EVENT_SHUTDOWN);
|
||||
replay_mutex_unlock();
|
||||
}
|
||||
}
|
||||
|
||||
bool replay_checkpoint(ReplayCheckpoint checkpoint)
|
||||
{
|
||||
bool res = false;
|
||||
assert(EVENT_CHECKPOINT + checkpoint <= EVENT_CHECKPOINT_LAST);
|
||||
replay_save_instructions();
|
||||
|
||||
if (!replay_file) {
|
||||
return true;
|
||||
}
|
||||
|
||||
replay_mutex_lock();
|
||||
|
||||
if (replay_mode == REPLAY_MODE_PLAY) {
|
||||
if (replay_next_event_is(EVENT_CHECKPOINT + checkpoint)) {
|
||||
replay_finish_event();
|
||||
} else if (replay_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_data_kind != EVENT_ASYNC;
|
||||
} else if (replay_mode == REPLAY_MODE_RECORD) {
|
||||
replay_put_event(EVENT_CHECKPOINT + checkpoint);
|
||||
replay_save_events(checkpoint);
|
||||
res = true;
|
||||
}
|
||||
out:
|
||||
replay_mutex_unlock();
|
||||
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_mutex_init();
|
||||
|
||||
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_data_kind = -1;
|
||||
replay_state.instructions_count = 0;
|
||||
replay_state.current_step = 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;
|
||||
|
||||
rr = qemu_opt_get(opts, "rr");
|
||||
if (!rr) {
|
||||
/* Just enabling icount */
|
||||
return;
|
||||
} 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_enable(fname, mode);
|
||||
}
|
||||
|
||||
void replay_start(void)
|
||||
{
|
||||
if (replay_mode == REPLAY_MODE_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (replay_blockers) {
|
||||
error_report("Record/replay: %s",
|
||||
error_get_pretty(replay_blockers->data));
|
||||
exit(1);
|
||||
}
|
||||
if (!use_icount) {
|
||||
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) {
|
||||
/* 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;
|
||||
}
|
||||
|
||||
replay_finish_events();
|
||||
replay_mutex_destroy();
|
||||
}
|
||||
|
||||
void replay_add_blocker(Error *reason)
|
||||
{
|
||||
replay_blockers = g_slist_prepend(replay_blockers, reason);
|
||||
}
|
@ -21,6 +21,8 @@ stub-obj-y += mon-printf.o
|
||||
stub-obj-y += monitor-init.o
|
||||
stub-obj-y += notify-event.o
|
||||
stub-obj-y += qtest.o
|
||||
stub-obj-y += replay.o
|
||||
stub-obj-y += replay-user.o
|
||||
stub-obj-y += reset.o
|
||||
stub-obj-y += runstate-check.o
|
||||
stub-obj-y += set-fd-handler.o
|
||||
|
32
stubs/replay-user.c
Normal file
32
stubs/replay-user.c
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 "sysemu/replay.h"
|
||||
|
||||
bool replay_exception(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool replay_has_exception(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool replay_interrupt(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool replay_has_interrupt(void)
|
||||
{
|
||||
return false;
|
||||
}
|
31
stubs/replay.c
Normal file
31
stubs/replay.c
Normal file
@ -0,0 +1,31 @@
|
||||
#include "sysemu/replay.h"
|
||||
#include <stdlib.h>
|
||||
#include "sysemu/sysemu.h"
|
||||
|
||||
ReplayMode replay_mode;
|
||||
|
||||
int64_t replay_save_clock(unsigned int kind, int64_t clock)
|
||||
{
|
||||
abort();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t replay_read_clock(unsigned int kind)
|
||||
{
|
||||
abort();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool replay_checkpoint(ReplayCheckpoint checkpoint)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool replay_events_enabled(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void replay_finish(void)
|
||||
{
|
||||
}
|
@ -1069,7 +1069,7 @@ TranslationBlock *tb_gen_code(CPUState *cpu,
|
||||
#endif
|
||||
|
||||
phys_pc = get_page_addr_code(env, pc);
|
||||
if (use_icount) {
|
||||
if (use_icount && !(cflags & CF_IGNORE_ICOUNT)) {
|
||||
cflags |= CF_USE_ICOUNT;
|
||||
}
|
||||
|
||||
|
27
ui/input.c
27
ui/input.c
@ -6,6 +6,7 @@
|
||||
#include "trace.h"
|
||||
#include "ui/input.h"
|
||||
#include "ui/console.h"
|
||||
#include "sysemu/replay.h"
|
||||
|
||||
struct QemuInputHandlerState {
|
||||
DeviceState *dev;
|
||||
@ -300,14 +301,10 @@ static void qemu_input_queue_sync(struct QemuInputEventQueueHead *queue)
|
||||
QTAILQ_INSERT_TAIL(queue, item, node);
|
||||
}
|
||||
|
||||
void qemu_input_event_send(QemuConsole *src, InputEvent *evt)
|
||||
void qemu_input_event_send_impl(QemuConsole *src, InputEvent *evt)
|
||||
{
|
||||
QemuInputHandlerState *s;
|
||||
|
||||
if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
qemu_input_event_trace(src, evt);
|
||||
|
||||
/* pre processing */
|
||||
@ -324,14 +321,19 @@ void qemu_input_event_send(QemuConsole *src, InputEvent *evt)
|
||||
s->events++;
|
||||
}
|
||||
|
||||
void qemu_input_event_sync(void)
|
||||
void qemu_input_event_send(QemuConsole *src, InputEvent *evt)
|
||||
{
|
||||
QemuInputHandlerState *s;
|
||||
|
||||
if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
replay_input_event(src, evt);
|
||||
}
|
||||
|
||||
void qemu_input_event_sync_impl(void)
|
||||
{
|
||||
QemuInputHandlerState *s;
|
||||
|
||||
trace_input_event_sync();
|
||||
|
||||
QTAILQ_FOREACH(s, &handlers, node) {
|
||||
@ -345,6 +347,15 @@ void qemu_input_event_sync(void)
|
||||
}
|
||||
}
|
||||
|
||||
void qemu_input_event_sync(void)
|
||||
{
|
||||
if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
replay_input_sync_event();
|
||||
}
|
||||
|
||||
InputEvent *qemu_input_event_new_key(KeyValue *key, bool down)
|
||||
{
|
||||
InputEvent *evt = g_new0(InputEvent, 1);
|
||||
|
60
vl.c
60
vl.c
@ -122,6 +122,8 @@ int main(int argc, char **argv)
|
||||
#include "qapi-event.h"
|
||||
#include "exec/semihost.h"
|
||||
#include "crypto/init.h"
|
||||
#include "sysemu/replay.h"
|
||||
#include "qapi/qmp/qerror.h"
|
||||
|
||||
#define MAX_VIRTIO_CONSOLES 1
|
||||
#define MAX_SCLP_CONSOLES 1
|
||||
@ -474,6 +476,12 @@ static QemuOptsList qemu_icount_opts = {
|
||||
}, {
|
||||
.name = "sleep",
|
||||
.type = QEMU_OPT_BOOL,
|
||||
}, {
|
||||
.name = "rr",
|
||||
.type = QEMU_OPT_STRING,
|
||||
}, {
|
||||
.name = "rrfile",
|
||||
.type = QEMU_OPT_STRING,
|
||||
},
|
||||
{ /* end of list */ }
|
||||
},
|
||||
@ -846,7 +854,11 @@ static void configure_rtc(QemuOpts *opts)
|
||||
if (!strcmp(value, "utc")) {
|
||||
rtc_utc = 1;
|
||||
} else if (!strcmp(value, "localtime")) {
|
||||
Error *blocker = NULL;
|
||||
rtc_utc = 0;
|
||||
error_setg(&blocker, QERR_REPLAY_NOT_SUPPORTED,
|
||||
"-rtc base=localtime");
|
||||
replay_add_blocker(blocker);
|
||||
} else {
|
||||
configure_rtc_date_offset(value, 0);
|
||||
}
|
||||
@ -1255,6 +1267,11 @@ static void smp_parse(QemuOpts *opts)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (smp_cpus > 1 || smp_cores > 1 || smp_threads > 1) {
|
||||
Error *blocker = NULL;
|
||||
error_setg(&blocker, QERR_REPLAY_NOT_SUPPORTED, "smp");
|
||||
replay_add_blocker(blocker);
|
||||
}
|
||||
}
|
||||
|
||||
static void realtime_init(void)
|
||||
@ -1641,15 +1658,21 @@ static void qemu_kill_report(void)
|
||||
static int qemu_reset_requested(void)
|
||||
{
|
||||
int r = reset_requested;
|
||||
reset_requested = 0;
|
||||
return r;
|
||||
if (r && replay_checkpoint(CHECKPOINT_RESET_REQUESTED)) {
|
||||
reset_requested = 0;
|
||||
return r;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int qemu_suspend_requested(void)
|
||||
{
|
||||
int r = suspend_requested;
|
||||
suspend_requested = 0;
|
||||
return r;
|
||||
if (r && replay_checkpoint(CHECKPOINT_SUSPEND_REQUESTED)) {
|
||||
suspend_requested = 0;
|
||||
return r;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static WakeupReason qemu_wakeup_requested(void)
|
||||
@ -1797,12 +1820,18 @@ void qemu_system_killed(int signal, pid_t pid)
|
||||
shutdown_signal = signal;
|
||||
shutdown_pid = pid;
|
||||
no_shutdown = 0;
|
||||
qemu_system_shutdown_request();
|
||||
|
||||
/* Cannot call qemu_system_shutdown_request directly because
|
||||
* we are in a signal handler.
|
||||
*/
|
||||
shutdown_requested = 1;
|
||||
qemu_notify_event();
|
||||
}
|
||||
|
||||
void qemu_system_shutdown_request(void)
|
||||
{
|
||||
trace_qemu_system_shutdown_request();
|
||||
replay_shutdown_request();
|
||||
shutdown_requested = 1;
|
||||
qemu_notify_event();
|
||||
}
|
||||
@ -3991,6 +4020,8 @@ int main(int argc, char **argv, char **envp)
|
||||
}
|
||||
}
|
||||
|
||||
replay_configure(icount_opts);
|
||||
|
||||
opts = qemu_get_machine_opts();
|
||||
optarg = qemu_opt_get(opts, "type");
|
||||
if (optarg) {
|
||||
@ -4424,9 +4455,10 @@ int main(int argc, char **argv, char **envp)
|
||||
}
|
||||
|
||||
/* open the virtual block devices */
|
||||
if (snapshot)
|
||||
qemu_opts_foreach(qemu_find_opts("drive"),
|
||||
drive_enable_snapshot, NULL, NULL);
|
||||
if (snapshot || replay_mode != REPLAY_MODE_NONE) {
|
||||
qemu_opts_foreach(qemu_find_opts("drive"), drive_enable_snapshot,
|
||||
NULL, NULL);
|
||||
}
|
||||
if (qemu_opts_foreach(qemu_find_opts("drive"), drive_init_func,
|
||||
&machine_class->block_default_type, NULL)) {
|
||||
exit(1);
|
||||
@ -4481,6 +4513,10 @@ int main(int argc, char **argv, char **envp)
|
||||
}
|
||||
qemu_add_globals();
|
||||
|
||||
/* This checkpoint is required by replay to separate prior clock
|
||||
reading from the other reads, because timer polling functions query
|
||||
clock values from the log. */
|
||||
replay_checkpoint(CHECKPOINT_INIT);
|
||||
qdev_machine_init();
|
||||
|
||||
current_machine->ram_size = ram_size;
|
||||
@ -4599,6 +4635,12 @@ int main(int argc, char **argv, char **envp)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
replay_start();
|
||||
|
||||
/* This checkpoint is required by replay to separate prior clock
|
||||
reading from the other reads, because timer polling functions query
|
||||
clock values from the log. */
|
||||
replay_checkpoint(CHECKPOINT_RESET);
|
||||
qemu_system_reset(VMRESET_SILENT);
|
||||
register_global_state();
|
||||
if (loadvm) {
|
||||
@ -4636,6 +4678,8 @@ int main(int argc, char **argv, char **envp)
|
||||
}
|
||||
|
||||
main_loop();
|
||||
replay_disable_events();
|
||||
|
||||
bdrv_close_all();
|
||||
pause_all_vcpus();
|
||||
res_free();
|
||||
|
Loading…
Reference in New Issue
Block a user