This patch introduces command line options for enabling recording or replaying virtual machine behavior. "-record" option starts recording of the execution and saves it into the log, specified with "fname" parameter. "-replay" option is intended for replaying previously saved log.
Signed-off-by: Pavel Dovgalyuk <pavel.dovga...@ispras.ru> --- cpus.c | 15 ++++++++-- qemu-options.hx | 27 +++++++++++++++++++ vl.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 116 insertions(+), 5 deletions(-) diff --git a/cpus.c b/cpus.c index 6adff2b..de8276b 100644 --- a/cpus.c +++ b/cpus.c @@ -933,12 +933,21 @@ static void qemu_wait_io_event_common(CPUState *cpu) static void qemu_tcg_wait_io_event(void) { CPUState *cpu; + GMainContext *context = g_main_context_default(); - while (all_cpu_threads_idle()) { - /* Start accounting real time to the virtual clock if the CPUs - are idle. */ + if (replay_mode == REPLAY_MODE_PLAY + && all_cpu_threads_idle() && first_cpu->halted) { + /* wakeup iothread when there is no code to execute in replay mode */ qemu_clock_warp(QEMU_CLOCK_VIRTUAL); + g_main_context_wakeup(context); qemu_cond_wait(tcg_halt_cond, &qemu_global_mutex); + } else { + while (all_cpu_threads_idle()) { + /* Start accounting real time to the virtual clock if the CPUs + are idle. */ + qemu_clock_warp(QEMU_CLOCK_VIRTUAL); + qemu_cond_wait(tcg_halt_cond, &qemu_global_mutex); + } } while (iothread_requesting_mutex) { diff --git a/qemu-options.hx b/qemu-options.hx index 22cf3b9..44ad8fc 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -3380,6 +3380,33 @@ Dump json-encoded vmstate information for current machine type to file in @var{file} ETEXI +DEF("record", HAS_ARG, QEMU_OPTION_record, + "-record fname=<filename>[,suffix=<suffix>,snapshot=<on/off>]\n" + " writes replay file for latter replaying\n", + QEMU_ARCH_ALL) +STEXI +@item -record fname=@var{file}[,suffix=@var{suffix},snapshot=@var{snapshot}] +Writes compact execution trace into @var{file}. +Changes for disk images are written +into separate files with @var{suffix} added. If no @var{suffix} is +specified, "replay_qcow" is used as suffix. +If @var{snapshot} parameter is set as off, then original disk image will be +modified. Default value is on. +ETEXI + +DEF("replay", HAS_ARG, QEMU_OPTION_replay, + "-replay fname=<filename>[,suffix=<suffix>,snapshot=<on/off>]\n" + " plays saved replay file\n", QEMU_ARCH_ALL) +STEXI +@item -replay fname=@var{filename}[,suffix=@var{suffix},snapshot=@var{snapshot}] +Plays compact execution trace from @var{filename}. +Changes for disk images and VM states are read +from separate files with @var{suffix} added. If no @var{suffix} is +specified, "replay_qcow" is used as suffix. +If @var{snapshot} parameter is set as off, then original disk image will be +modified. Default value is on. +ETEXI + HXCOMM This is the last statement. Insert new options before this line! STEXI @end table diff --git a/vl.c b/vl.c index 877a77c..fb5003e 100644 --- a/vl.c +++ b/vl.c @@ -548,6 +548,42 @@ static QemuOptsList qemu_icount_opts = { }, }; +static QemuOptsList qemu_record_opts = { + .name = "record", + .head = QTAILQ_HEAD_INITIALIZER(qemu_record_opts.head), + .desc = { + { + .name = "fname", + .type = QEMU_OPT_STRING, + },{ + .name = "suffix", + .type = QEMU_OPT_STRING, + },{ + .name = "snapshot", + .type = QEMU_OPT_BOOL, + }, + { /* end of list */ } + }, +}; + +static QemuOptsList qemu_replay_opts = { + .name = "replay", + .head = QTAILQ_HEAD_INITIALIZER(qemu_replay_opts.head), + .desc = { + { + .name = "fname", + .type = QEMU_OPT_STRING, + },{ + .name = "suffix", + .type = QEMU_OPT_STRING, + },{ + .name = "snapshot", + .type = QEMU_OPT_BOOL, + }, + { /* end of list */ } + }, +}; + /** * Get machine options * @@ -2711,7 +2747,9 @@ out: int main(int argc, char **argv, char **envp) { int i; - int snapshot, linux_boot; + int snapshot, linux_boot, replay_snapshot; + int not_compatible_replay_param = 0; + const char *icount_option = NULL; const char *initrd_filename; const char *kernel_filename, *kernel_cmdline; const char *boot_order; @@ -2784,6 +2822,8 @@ int main(int argc, char **argv, char **envp) qemu_add_opts(&qemu_name_opts); qemu_add_opts(&qemu_numa_opts); qemu_add_opts(&qemu_icount_opts); + qemu_add_opts(&qemu_replay_opts); + qemu_add_opts(&qemu_record_opts); runstate_init(); @@ -2797,6 +2837,7 @@ int main(int argc, char **argv, char **envp) cpu_model = NULL; ram_size = default_ram_size; snapshot = 0; + replay_snapshot = 1; cyls = heads = secs = 0; translation = BIOS_ATA_TRANSLATION_AUTO; @@ -2914,6 +2955,7 @@ int main(int argc, char **argv, char **envp) break; case QEMU_OPTION_pflash: drive_add(IF_PFLASH, -1, optarg, PFLASH_OPTS); + not_compatible_replay_param++; break; case QEMU_OPTION_snapshot: snapshot = 1; @@ -3070,6 +3112,7 @@ int main(int argc, char **argv, char **envp) #endif case QEMU_OPTION_bt: add_device_config(DEV_BT, optarg); + not_compatible_replay_param++; break; case QEMU_OPTION_audio_help: AUD_help (); @@ -3284,6 +3327,7 @@ int main(int argc, char **argv, char **envp) if (!opts) { exit(1); } + not_compatible_replay_param++; break; case QEMU_OPTION_fsdev: olist = qemu_find_opts("fsdev"); @@ -3412,6 +3456,7 @@ int main(int argc, char **argv, char **envp) if (strncmp(optarg, "mon:", 4) == 0) { default_monitor = 0; } + not_compatible_replay_param++; break; case QEMU_OPTION_debugcon: add_device_config(DEV_DEBUGCON, optarg); @@ -3532,6 +3577,7 @@ int main(int argc, char **argv, char **envp) if (!qemu_opts_parse(qemu_find_opts("smp-opts"), optarg, 1)) { exit(1); } + not_compatible_replay_param++; break; case QEMU_OPTION_vnc: #ifdef CONFIG_VNC @@ -3770,6 +3816,24 @@ int main(int argc, char **argv, char **envp) exit(1); } break; + case QEMU_OPTION_record: + opts = qemu_opts_parse(qemu_find_opts("record"), optarg, 0); + if (!opts) { + fprintf(stderr, "Invalid record options: %s\n", optarg); + exit(1); + } + replay_configure(opts, REPLAY_MODE_RECORD); + replay_snapshot = qemu_opt_get_bool(opts, "snapshot", 1); + break; + case QEMU_OPTION_replay: + opts = qemu_opts_parse(qemu_find_opts("replay"), optarg, 0); + if (!opts) { + fprintf(stderr, "Invalid replay options: %s\n", optarg); + exit(1); + } + replay_configure(opts, REPLAY_MODE_PLAY); + replay_snapshot = qemu_opt_get_bool(opts, "snapshot", 1); + break; default: os_parse_cmd_args(popt->index, optarg); } @@ -3784,6 +3848,12 @@ int main(int argc, char **argv, char **envp) exit(1); } + if (not_compatible_replay_param && (replay_mode != REPLAY_MODE_NONE)) { + fprintf(stderr, "options -smp, -pflash, -chardev, -bt, -parallel " + "are not compatible with record/replay\n"); + exit(1); + } + if (qemu_opts_foreach(qemu_find_opts("sandbox"), parse_sandbox, NULL, 0)) { exit(1); } @@ -4125,6 +4195,11 @@ int main(int argc, char **argv, char **envp) qemu_opts_del(icount_opts); } + if (replay_mode != REPLAY_MODE_NONE && !use_icount) { + fprintf(stderr, "Please enable icount to use record/replay\n"); + exit(1); + } + /* clean up network at qemu process termination */ atexit(&net_cleanup); @@ -4161,7 +4236,7 @@ int main(int argc, char **argv, char **envp) } /* open the virtual block devices */ - if (snapshot) + if (snapshot || (replay_mode != REPLAY_MODE_NONE && replay_snapshot)) qemu_opts_foreach(qemu_find_opts("drive"), drive_enable_snapshot, NULL, 0); if (qemu_opts_foreach(qemu_find_opts("drive"), drive_init_func, &machine_class->block_default_type, 1) != 0) {