From patchwork Tue Jul 1 11:31:57 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pavel Dovgalyuk X-Patchwork-Id: 366062 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id DA35C14008B for ; Tue, 1 Jul 2014 21:33:02 +1000 (EST) Received: from localhost ([::1]:46321 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1X1wJ3-0006JN-3Z for incoming@patchwork.ozlabs.org; Tue, 01 Jul 2014 07:33:01 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:60674) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1X1wI9-0005EL-T6 for qemu-devel@nongnu.org; Tue, 01 Jul 2014 07:32:13 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1X1wI1-0004AG-Ub for qemu-devel@nongnu.org; Tue, 01 Jul 2014 07:32:05 -0400 Received: from mail.ispras.ru ([83.149.199.45]:33261) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1X1wI0-00048f-UM for qemu-devel@nongnu.org; Tue, 01 Jul 2014 07:31:57 -0400 Received: from PASHAISP (unknown [80.250.189.177]) by mail.ispras.ru (Postfix) with ESMTPSA id DF460540159 for ; Tue, 1 Jul 2014 15:31:55 +0400 (MSK) From: "Pavel Dovgaluk" To: "'QEMU Developers'" Date: Tue, 1 Jul 2014 15:31:57 +0400 Message-ID: <008701cf9520$113aec00$33b0c400$@Dovgaluk@ispras.ru> MIME-Version: 1.0 X-Mailer: Microsoft Office Outlook 12.0 Thread-Index: Ac+VIBDpebPN+o4qSSe9EPjvnO7vxw== Content-Language: ru X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 83.149.199.45 Subject: [Qemu-devel] [RFC PATCH 19/22] Deterministic replay core functions X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Functions for writing and reading replay log. These functions are used to record the following events: * Output of the time() function * Sound in and sound out events in winaudio * Hardware interrupts and exceptions * Special replay debug assert * Target time information used for reviewing the events log * Start and finish of saving VM * Synchronous data from some module * Execution of the group of the instructions * Hardware clock read (real ticks, host clock, virtual clock) * Asynchronous events: - Setting event notifier - Network packet - Worker thread invocation - BH execution - USB packets - Serial port input - Mouse and keyboard input * Checkpoint in the code (used to synchronize the events) * System shutdown This module also sets timer for periodical snapshotting of the system state, when it is required by user. Signed-off-by: Pavel Dovgalyuk --- +void replay_change(struct Monitor *mon); +#endif diff --git a/replay/Makefile.objs b/replay/Makefile.objs new file mode 100644 index 0000000..926d50e --- /dev/null +++ b/replay/Makefile.objs @@ -0,0 +1 @@ +obj-y += replay.o replay-events.o replay-internal.o replay-usb.o replay-icount.o replay-audio.o replay-char.o replay-debug.o replay-input.o replay-net.o diff --git a/replay/replay-audio.c b/replay/replay-audio.c new file mode 100644 index 0000000..9814a25 --- /dev/null +++ b/replay/replay-audio.c @@ -0,0 +1,238 @@ +#include "qemu-common.h" +#include "exec/cpu-common.h" +#include "replay.h" +#include "replay-internal.h" +#include "qemu/log.h" + +/* Sound card state */ +typedef struct +{ + void *instance; + const int event_id; + //void (const *callback)(void *, WAVEHDR *); +#ifdef _WIN32 + WAVEHDR *queue; +#endif + /*! Maximum size of the queue */ + int size; + /*! Current size of the queue */ + sig_atomic_t cur_size; + unsigned int head, tail; +} SoundQueue; + + +static SoundQueue sound_in = { + .event_id = EVENT_SOUND_IN + }, + sound_out = { + .event_id = EVENT_SOUND_OUT, + //.callback = winwave_callback_out_impl + }; + +#ifdef _WIN32 +/*! Spinlock for sound events processing. */ +static spinlock_t sound_lock = SPIN_LOCK_UNLOCKED; +#endif + +/***************************************************************************** + * Sound queue functions * + *****************************************************************************/ + +/* callback functions */ +#ifdef _WIN32 +void winwave_callback_out_impl(void *dwInstance, WAVEHDR *h); +void winwave_callback_in_impl(void *dwInstance, WAVEHDR *h); + +void replay_init_sound_in(void *instance, WAVEHDR *hdrs, int sz) +{ + sound_in.instance = instance; + sound_in.queue = hdrs; + sound_in.size = sz; + sound_in.head = 0; + sound_in.tail = 0; + sound_in.cur_size = 0; +} + +void replay_init_sound_out(void *instance, WAVEHDR *hdrs, int sz) +{ + sound_out.instance = instance; + sound_out.queue = hdrs; + sound_out.size = sz; + sound_out.head = 0; + sound_out.tail = 0; + sound_out.cur_size = 0; +} + +static int sound_queue_add(SoundQueue *q, WAVEHDR *hdr) +{ + if (q->queue + q->tail != hdr) + { + // state was loaded and we need to reset the queue + if (q->cur_size == 0) + { + q->head = q->tail = hdr - q->queue; + } + else + { + fprintf(stderr, "Replay: Sound queue error\n"); + exit(1); + } + } + + if (q->cur_size == q->size) + { + if (replay_mode == REPLAY_PLAY) + { + return 1; + } + + fprintf(stderr, "Replay: Sound queue overflow\n"); + exit(1); + } + + q->tail = (q->tail + 1) % q->size; + ++q->cur_size; + + return 0; +} + +void replay_save_sound_out(void) +{ + spin_lock(&sound_lock); + while (sound_out.cur_size != 0) + { + //printf("Saving from output queue %x\n", &sound_out.queue[sound_out.head]); + // put the message ID + replay_put_event(sound_out.event_id); + // save the buffer size + replay_put_dword(sound_out.queue[sound_out.head].dwBytesRecorded); + // perform winwave-specific actions + winwave_callback_out_impl(sound_out.instance, &sound_out.queue[sound_out.head]); + // goto the next buffer + sound_out.head = (sound_out.head + 1) % sound_out.size; + /* TODO!!! Maybe need some kind of lock here in SAVE mode ?? */ + --sound_out.cur_size; + } + spin_unlock(&sound_lock); +} + + +void replay_save_sound_in(void) +{ + spin_lock(&sound_lock); + while (sound_in.cur_size != 0) + { + //printf("Saving from input queue %x\n", &sound_in.queue[sound_in.head]); + + // put the message ID + replay_put_event(sound_in.event_id); + // save the buffer + replay_put_array(sound_in.queue[sound_in.head].lpData, + sound_in.queue[sound_in.head].dwBytesRecorded); + // perform winwave-specific actions + winwave_callback_in_impl(sound_in.instance, &sound_in.queue[sound_in.head]); + // goto the next buffer + sound_in.head = (sound_in.head + 1) % sound_in.size; + /* TODO!!! Maybe need some kind of lock here in SAVE mode ?? */ + --sound_in.cur_size; + } + spin_unlock(&sound_lock); +} + + +void replay_read_sound_out(void) +{ + if (sound_out.cur_size == 0) + { + fprintf(stderr, "Replay: Sound queue underflow\n"); + exit(1); + } + + // get the buffer size + sound_out.queue[sound_out.head].dwBytesRecorded = replay_get_dword(); + + replay_check_error(); + replay_has_unread_data = 0; + + // perform winwave-specific actions + winwave_callback_out_impl(sound_out.instance, &sound_out.queue[sound_out.head]); + sound_out.head = (sound_out.head + 1) % sound_out.size; + --sound_out.cur_size; +} + +void replay_read_sound_in(void) +{ + if (sound_in.cur_size == 0) + { + fprintf(stderr, "Replay: Sound queue underflow\n"); + exit(1); + } + + // get the buffer size + size_t size; + replay_get_array(sound_in.queue[sound_in.head].lpData, &size); + sound_in.queue[sound_in.head].dwBytesRecorded = (unsigned int)size; + + replay_check_error(); + replay_has_unread_data = 0; + + // perform winwave-specific actions + winwave_callback_in_impl(sound_in.instance, &sound_in.queue[sound_in.head]); + sound_in.head = (sound_in.head + 1) % sound_in.size; + --sound_in.cur_size; +} + + +void replay_sound_in_event(WAVEHDR *hdr) +{ + spin_lock(&sound_lock); + if (sound_queue_add(&sound_in, hdr)) + { + fprintf(stderr, "Replay: Input sound buffer overflow\n"); + exit(1); + } + spin_unlock(&sound_lock); +} + + + +int replay_sound_out_event(WAVEHDR *hdr) +{ + spin_lock(&sound_lock); + int result = sound_queue_add(&sound_out, hdr); + spin_unlock(&sound_lock); + + return result; +} +#endif + +bool replay_has_sound_events(void) +{ + return sound_in.cur_size || sound_out.cur_size; +} + +void replay_sound_flush_queue(void) +{ +#ifdef _WIN32 + spin_lock(&sound_lock); + while (sound_out.cur_size != 0) + { + // perform winwave-specific actions + winwave_callback_out_impl(sound_out.instance, &sound_out.queue[sound_out.head]); + // goto the next buffer + sound_out.head = (sound_out.head + 1) % sound_out.size; + /* TODO!!! Maybe need some kind of lock here in SAVE mode ?? */ + --sound_out.cur_size; + } + while (sound_in.cur_size != 0) + { + // perform winwave-specific actions + winwave_callback_in_impl(sound_in.instance, &sound_in.queue[sound_in.head]); + // goto the next buffer + sound_in.head = (sound_in.head + 1) % sound_in.size; + --sound_in.cur_size; + } + spin_unlock(&sound_lock); +#endif +} + diff --git a/replay/replay-char.c b/replay/replay-char.c new file mode 100644 index 0000000..99c4934 --- /dev/null +++ b/replay/replay-char.c @@ -0,0 +1,119 @@ +#include +#include +#include + +#include "replay.h" +#include "replay-internal.h" +#include "sysemu/sysemu.h" +#include "sysemu/char.h" + +#include "qemu/log.h" + +#define MAX_CHAR_DRIVERS MAX_SERIAL_PORTS +/* Char drivers that generate qemu_chr_be_write events + that should be saved into the log. */ +static CharDriverState *char_drivers[MAX_CHAR_DRIVERS]; + +/* Char event attributes. */ +typedef struct CharEvent +{ + int id; + uint8_t *buf; + size_t len; +} CharEvent; + +static int find_char_driver(CharDriverState *chr) +{ + int i = 0; + while (i < MAX_CHAR_DRIVERS && char_drivers[i] != chr) + ++i; + + return i >= MAX_CHAR_DRIVERS ? -1 : i; +} + + +void replay_register_char_driver(CharDriverState *chr) +{ + chr->replay = true; + int i = find_char_driver(NULL); + + if (i < 0) + { + fprintf(stderr, "Replay: cannot register char driver\n"); + exit(1); + } + else + { + + char_drivers[i] = chr; + } +} + +void replay_delayed_register_char_driver(void) +{ + int i; + for (i = 0; i < MAX_SERIAL_PORTS; i++) + if (serial_hds[i] != NULL) + { + serial_hds[i]->replay = true; + replay_register_char_driver(serial_hds[i]); + } +} + +void replay_chr_be_write(CharDriverState *s, uint8_t *buf, int len) +{ + CharEvent *event = g_malloc0(sizeof(CharEvent)); + + event->id = find_char_driver(s); + if (event->id < 0) + { + fprintf(stderr, "Replay: cannot find char driver\n"); + exit(1); + } + event->buf = g_malloc(len); + memcpy(event->buf, buf, len); + event->len = len; + + replay_add_event(REPLAY_ASYNC_EVENT_CHAR, event); +} + +void replay_event_char_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_save(void *opaque) +{ + CharEvent *event = (CharEvent*)opaque; + + replay_put_byte(event->id); + replay_put_array(event->buf, event->len); +} + +void *replay_event_char_read(void) +{ + CharEvent *event = g_malloc0(sizeof(CharEvent)); + + event->id = replay_get_byte(); + replay_get_array_alloc(&event->buf, &event->len); + + return event; +} + +void replay_event_char_skip(void) +{ + size_t size; + + fseek(replay_file, sizeof(uint8_t), SEEK_CUR); + if (fread(&size, sizeof(size), 1, replay_file) != 1) + { + fprintf(stderr, "Internal error while skipping char event\n"); + exit(1); + } + fseek(replay_file, size, SEEK_CUR); +} diff --git a/replay/replay-debug.c b/replay/replay-debug.c new file mode 100644 index 0000000..5d1cc95 --- /dev/null +++ b/replay/replay-debug.c @@ -0,0 +1,152 @@ +#include "qemu-common.h" +#include "exec/cpu-common.h" +#include "exec/cpu-defs.h" + +#include "replay.h" +#include "replay-internal.h" + +/* Reverse debugging data */ + +/* Saved handler of the debug exception */ +static CPUDebugExcpHandler *prev_debug_excp_handler; +/* Step of the last breakpoint hit. Used for seeking in reverse continue mode. */ +static uint64_t last_breakpoint_step; +/* Start step, where reverse continue begins, + or target step for reverse stepping.*/ +static uint64_t last_reverse_step; +/* Start step, where reverse continue begins.*/ +static uint64_t start_reverse_step; +/* Previously loaded step for reverse continue */ +static SavedStateInfo *reverse_state; + +/*! Breakpoint handler for pass2 of reverse continue. + Stops the execution at previously saved breakpoint step. */ +static void reverse_continue_pass2_breakpoint_handler(CPUArchState *env) +{ + if (replay_get_current_step() == last_breakpoint_step) + { + CPUState *cpu = ENV_GET_CPU(env); + CPUDebugExcpHandler *handler = prev_debug_excp_handler; + prev_debug_excp_handler = NULL; + + play_submode = REPLAY_PLAY_NORMAL; + + cpu->exception_index = EXCP_DEBUG; + // invoke the breakpoint + cpu_set_debug_excp_handler(handler); + handler(env); + cpu_exit(cpu); + } +} + +/*! Breakpoint handler for pass1 of reverse continue. + Saves last breakpoint hit and switches to pass2 when starting point is reached. */ +static void reverse_continue_pass1_breakpoint_handler(CPUArchState *env) +{ + if (replay_get_current_step() == last_reverse_step) + { + CPUState *cpu = ENV_GET_CPU(env); + // repeat first pass if breakpoint was not found + // on current iteration + if (last_breakpoint_step == reverse_state->step - 1 + && reverse_state != saved_states) + { + last_reverse_step = reverse_state->step; + // load previous state + --reverse_state; + last_breakpoint_step = reverse_state->step - 1; + replay_seek_step(reverse_state->step); + // set break should be after seek, because seek resets break + replay_set_break(last_reverse_step); + cpu_loop_exit(cpu); + } + else + { + // this condition is needed, when no breakpoints were found + if (last_breakpoint_step == reverse_state->step - 1) + { + ++last_breakpoint_step; + } + cpu_set_debug_excp_handler(reverse_continue_pass2_breakpoint_handler); + + reverse_continue_pass2_breakpoint_handler(env); + replay_seek_step(last_breakpoint_step); + cpu_loop_exit(cpu); + } + } + else + { + // watchpoint branch + last_breakpoint_step = replay_get_current_step(); + } +} + + +void replay_reverse_breakpoint(void) +{ + // we started reverse execution from a breakpoint + if (replay_get_current_step() != start_reverse_step) + { + last_breakpoint_step = replay_get_current_step(); + } +} + + +void replay_reverse_continue(void) +{ + if (replay_mode == REPLAY_PLAY && play_submode == REPLAY_PLAY_NORMAL) + { + tb_flush_all(); + play_submode = REPLAY_PLAY_REVERSE; + + last_reverse_step = replay_get_current_step(); + start_reverse_step = replay_get_current_step(); + // load initial state + reverse_state = find_nearest_state(replay_get_current_step()); + replay_seek_step(reverse_state->step); + // run to current step + replay_set_break(last_reverse_step); + // decrement to allow breaking at the first step + last_breakpoint_step = reverse_state->step - 1; + prev_debug_excp_handler = + cpu_set_debug_excp_handler(reverse_continue_pass1_breakpoint_handler); + } +} + + +/*! Breakpoint handler for reverse stepping. + Stops at the desired step and skips other breakpoints. */ +static void reverse_step_breakpoint_handler(CPUArchState *env) +{ + if (replay_get_current_step() == last_reverse_step) + { + CPUState *cpu = ENV_GET_CPU(env); + CPUDebugExcpHandler *handler = prev_debug_excp_handler; + prev_debug_excp_handler = NULL; + + play_submode = REPLAY_PLAY_NORMAL; + + cpu->exception_index = EXCP_DEBUG; + // invoke the breakpoint + cpu_set_debug_excp_handler(handler); + handler(env); + cpu_exit(cpu); + } +} + + +void replay_reverse_step(void) +{ + if (replay_mode == REPLAY_PLAY && play_submode == REPLAY_PLAY_NORMAL + && replay_get_current_step() > 0) + { + tb_flush_all(); + play_submode = REPLAY_PLAY_REVERSE; + + last_reverse_step = replay_get_current_step() - 1; + replay_seek_step(last_reverse_step); + + prev_debug_excp_handler = + cpu_set_debug_excp_handler(reverse_step_breakpoint_handler); + } +} diff --git a/replay/replay-events.c b/replay/replay-events.c new file mode 100644 index 0000000..bd2d53f --- /dev/null +++ b/replay/replay-events.c @@ -0,0 +1,463 @@ +#include +#include "qemu-common.h" +#include "exec/cpu-common.h" +#include "qemu/queue.h" +#include "replay.h" +#include "replay-internal.h" +#include "qemu/log.h" +#include "qemu/event_notifier.h" +#include "monitor/monitor.h" +#include "block/thread-pool.h" +#include "block/aio.h" +#include "ui/input.h" + +typedef struct Event +{ + unsigned int event_id; + void *opaque; + void *opaque2; + uint64_t id; + + QTAILQ_ENTRY(Event) events; +} Event; + +static QTAILQ_HEAD(, Event) events_list = QTAILQ_HEAD_INITIALIZER(events_list); + +static QemuMutex lock; +static unsigned int read_event_id = -1; +static uint64_t read_bh_id = -1; +static int read_opt = -1; + +// Externals +bool replay_events_enabled = false; + +// Functions + +static void replay_run_event(Event *event) +{ + switch (event->event_id) + { + case REPLAY_ASYNC_EVENT_NOTIFIER: + event_notifier_set((EventNotifier*)event->opaque); + break; + case REPLAY_ASYNC_EVENT_NETWORK: + replay_net_send_packet(event->opaque); + break; + case REPLAY_ASYNC_EVENT_THREAD: + thread_pool_work((struct ThreadPool *)event->opaque, (struct ThreadPoolElement *)event->opaque2); + break; + case REPLAY_ASYNC_EVENT_BH: + aio_bh_call(event->opaque); + break; +#ifdef CONFIG_USB_LIBUSB + case REPLAY_ASYNC_EVENT_USB_CTRL: + replay_event_usb_ctrl(event->opaque); + break; + case REPLAY_ASYNC_EVENT_USB_DATA: + replay_event_usb_data(event->opaque); + break; + case REPLAY_ASYNC_EVENT_USB_ISO: + replay_event_usb_iso(event->opaque); + break; +#endif + case REPLAY_ASYNC_EVENT_CHAR: + replay_event_char_run(event->opaque); + break; + case REPLAY_ASYNC_EVENT_INPUT: + qemu_input_event_send_impl(NULL, (InputEvent*)event->opaque); + // Using local variables, when replaying. Do not free them. + if (replay_mode == REPLAY_SAVE) + { + qapi_free_InputEvent((InputEvent*)event->opaque); + } + break; + case REPLAY_ASYNC_EVENT_INPUT_SYNC: + qemu_input_event_sync_impl(); + break; + default: + fprintf(stderr, "Replay: invalid async event ID (%d) in the queue\n", event->event_id); + exit(1); + break; + } +} + +void replay_enable_events(void) +{ + replay_events_enabled = true; +} + +void replay_flush_events(void) +{ + qemu_mutex_lock(&lock); + while (!QTAILQ_EMPTY(&events_list)) + { + Event *event = QTAILQ_FIRST(&events_list); + + // perform an action + replay_run_event(event); + + // go to next event + QTAILQ_REMOVE(&events_list, event, events); + + g_free(event); + } + qemu_mutex_unlock(&lock); +} + +void replay_disable_events(void) +{ + replay_events_enabled = false; + // Flush events queue before waiting of completion + replay_flush_events(); +} + +void replay_clear_events(void) +{ + qemu_mutex_lock(&lock); + while (!QTAILQ_EMPTY(&events_list)) + { + Event *event = QTAILQ_FIRST(&events_list); + // go to next event + QTAILQ_REMOVE(&events_list, event, events); + + g_free(event); + } + qemu_mutex_unlock(&lock); +} + +static void replay_add_event_internal(unsigned int event_id, void *opaque, void *opaque2, uint64_t id) +{ + if (event_id >= REPLAY_ASYNC_COUNT) + { + fprintf(stderr, "Replay: invalid async event ID (%d)\n", event_id); + exit(1); + } + if (!replay_file || replay_mode == REPLAY_NONE + || !replay_events_enabled + || replay_get_play_submode() == REPLAY_PLAY_CHANGED) + { + Event e; + e.event_id = event_id; + e.opaque = opaque; + e.opaque2 = opaque2; + e.id = id; + replay_run_event(&e); + return; + } + + Event *event = g_malloc0(sizeof(Event)); + event->event_id = event_id; + event->opaque = opaque; + event->opaque2 = opaque2; + event->id = id; + + qemu_mutex_lock(&lock); + QTAILQ_INSERT_TAIL(&events_list, event, events); + qemu_mutex_unlock(&lock); +} + +void replay_add_event(unsigned int event_id, void *opaque) +{ + replay_add_event_internal(event_id, opaque, NULL, 0); +} + +void replay_add_usb_event(unsigned int event_id, uint64_t id, void *opaque) +{ + replay_add_event_internal(event_id, opaque, NULL, id); +} + +void replay_add_bh_event(void *bh, uint64_t id) +{ + replay_add_event_internal(REPLAY_ASYNC_EVENT_BH, bh, NULL, id); +} + +void replay_add_event2(unsigned int event_id, void *opaque, void *opaque2) +{ + replay_add_event_internal(event_id, opaque, opaque2, 0); +} + +void replay_add_thread_event(void *opaque, void *opaque2, uint64_t id) +{ + replay_add_event_internal(REPLAY_ASYNC_EVENT_THREAD, opaque, opaque2, id); +} + +void replay_save_events(int opt) +{ + qemu_mutex_lock(&lock); + while (!QTAILQ_EMPTY(&events_list)) + { + Event *event = QTAILQ_FIRST(&events_list); + if (replay_mode != REPLAY_PLAY) + { + // put the event into the file + if (opt == -1) + { + replay_put_event(EVENT_ASYNC); + } + else + { + replay_put_event(EVENT_ASYNC_OPT); + replay_put_byte(opt); + } + replay_put_dword(event->event_id); + + // save event-specific data + switch (event->event_id) + { + case REPLAY_ASYNC_EVENT_NETWORK: + replay_net_save_packet(event->opaque); + break; +#ifdef CONFIG_USB_LIBUSB + case REPLAY_ASYNC_EVENT_USB_CTRL: + case REPLAY_ASYNC_EVENT_USB_DATA: + replay_put_qword(event->id); + replay_event_save_usb_xfer(event->opaque); + break; + case REPLAY_ASYNC_EVENT_USB_ISO: + replay_put_qword(event->id); + replay_event_save_usb_iso_xfer(event->opaque); + break; +#endif + case REPLAY_ASYNC_EVENT_THREAD: + case REPLAY_ASYNC_EVENT_BH: + replay_put_qword(event->id); + break; + case REPLAY_ASYNC_EVENT_CHAR: + replay_event_char_save(event->opaque); + break; + case REPLAY_ASYNC_EVENT_INPUT: + replay_save_input_event(event->opaque); + break; + } + } + + // perform an action + replay_run_event(event); + + // go to next event + QTAILQ_REMOVE(&events_list, event, events); + + g_free(event); + } + qemu_mutex_unlock(&lock); +} + +bool replay_has_events(void) +{ + return !QTAILQ_EMPTY(&events_list); +} + + +void replay_read_events(int opt) +{ + replay_fetch_data_kind(); + while ((opt == -1 && replay_data_kind == EVENT_ASYNC) + || (opt != -1 && replay_data_kind == EVENT_ASYNC_OPT)) + { + if (read_event_id == -1) + { + if (opt != -1) + { + read_opt = replay_get_byte(); + } + read_event_id = replay_get_dword(); + read_bh_id = -1; + replay_check_error(); + } + + if (opt != read_opt) + break; + // Execute some events without searching them in the queue + Event e; + switch (read_event_id) + { + case REPLAY_ASYNC_EVENT_NOTIFIER: + // Nothing to read + break; + case REPLAY_ASYNC_EVENT_NETWORK: + e.opaque = replay_net_read_packet(); + e.event_id = read_event_id; + replay_run_event(&e); + + replay_has_unread_data = 0; + read_event_id = -1; + read_opt = -1; + replay_fetch_data_kind(); + // continue with the next event + continue; + case REPLAY_ASYNC_EVENT_THREAD: + case REPLAY_ASYNC_EVENT_BH: + if (read_bh_id == -1) + { + read_bh_id = replay_get_qword(); + } + break; +#ifdef CONFIG_USB_LIBUSB + case REPLAY_ASYNC_EVENT_USB_CTRL: + case REPLAY_ASYNC_EVENT_USB_DATA: + case REPLAY_ASYNC_EVENT_USB_ISO: + if (read_bh_id == -1) + { + read_bh_id = replay_get_qword(); + } + // read after finding event in the list + break; +#endif + case REPLAY_ASYNC_EVENT_CHAR: + e.event_id = read_event_id; + e.opaque = replay_event_char_read(); + + replay_has_unread_data = 0; + read_event_id = -1; + read_opt = -1; + replay_fetch_data_kind(); + + replay_run_event(&e); + // continue with the next event + continue; + case REPLAY_ASYNC_EVENT_INPUT: + e.event_id = read_event_id; + e.opaque = replay_read_input_event(); + + replay_run_event(&e); + + replay_has_unread_data = 0; + read_event_id = -1; + read_opt = -1; + replay_fetch_data_kind(); + // continue with the next event + continue; + case REPLAY_ASYNC_EVENT_INPUT_SYNC: + e.event_id = read_event_id; + e.opaque = 0; + replay_run_event(&e); + + replay_has_unread_data = 0; + read_event_id = -1; + read_opt = -1; + replay_fetch_data_kind(); + // continue with the next event + continue; + default: + fprintf(stderr, "Unknown ID %d of replay event\n", read_event_id); + exit(1); + break; + } + + qemu_mutex_lock(&lock); + + Event *event = NULL; + Event *curr = NULL; + QTAILQ_FOREACH(curr, &events_list, events) + { + if (curr->event_id == read_event_id + && (read_bh_id == -1 || read_bh_id == curr->id)) + { + event = curr; + break; + } + } + + if (event) + { + // continue reading data +#ifdef CONFIG_USB_LIBUSB + switch (read_event_id) + { + case REPLAY_ASYNC_EVENT_USB_CTRL: + case REPLAY_ASYNC_EVENT_USB_DATA: + replay_event_read_usb_xfer(event->opaque); + break; + case REPLAY_ASYNC_EVENT_USB_ISO: + replay_event_read_usb_iso_xfer(event->opaque); + break; + } +#endif + + QTAILQ_REMOVE(&events_list, event, events); + + qemu_mutex_unlock(&lock); + + // reset unread data and other parameters to allow + // reading other data from the log while + // running the event + replay_has_unread_data = 0; + read_event_id = -1; + read_bh_id = -1; + read_opt = -1; + + replay_run_event(event); + g_free(event); + + replay_fetch_data_kind(); + } + else + { + qemu_mutex_unlock(&lock); + // No such event found in the queue + break; + } + } +} + + +void replay_init_events(void) +{ + read_event_id = -1; + qemu_mutex_init(&lock); +} + + +void replay_finish_events(void) +{ + replay_events_enabled = false; + replay_clear_events(); + qemu_mutex_destroy(&lock); +} + + +void replay_skip_async_event(struct Monitor *mon, uint64_t step, bool opt) +{ + if (opt) + { + replay_get_byte(); + } + + int id = replay_get_dword(); + switch (id) + { + case REPLAY_ASYNC_EVENT_NOTIFIER: + break; + case REPLAY_ASYNC_EVENT_NETWORK: + replay_net_skip_packet(mon, step); + break; + case REPLAY_ASYNC_EVENT_THREAD: + case REPLAY_ASYNC_EVENT_BH: + fseek(replay_file, sizeof(uint64_t), SEEK_CUR); + break; +#ifdef CONFIG_USB_LIBUSB + case REPLAY_ASYNC_EVENT_USB_CTRL: + case REPLAY_ASYNC_EVENT_USB_DATA: + replay_get_qword(); + replay_event_skip_usb_xfer(); + break; + case REPLAY_ASYNC_EVENT_USB_ISO: + replay_get_qword(); + replay_event_skip_usb_iso_xfer(); + break; +#endif + case REPLAY_ASYNC_EVENT_CHAR: + replay_event_char_skip(); + break; + case REPLAY_ASYNC_EVENT_INPUT: + replay_skip_input_event(mon, step); + break; + case REPLAY_ASYNC_EVENT_INPUT_SYNC: + break; + default: + fprintf(stderr, "Unknown ID %d of replay event found while skipping\n", read_event_id); + exit(1); + break; + } +} + diff --git a/replay/replay-icount.c b/replay/replay-icount.c new file mode 100644 index 0000000..c216a94 --- /dev/null +++ b/replay/replay-icount.c @@ -0,0 +1,112 @@ +#include +#include +#include +#include + +#include "qemu-common.h" +#include "sysemu/cpus.h" +#include "sysemu/sysemu.h" +#include "qemu/timer.h" +#include "migration/vmstate.h" +#include "replay.h" +#include "replay-internal.h" + +int replay_icount; + +typedef struct { + /* Compensate for varying guest execution speed. */ + int64_t bias; + /* Timer for advancing VM clock, when all CPUs are sleeping */ + QEMUTimer *icount_warp_timer; + int64_t vm_clock_warp_start; +} ReplayIcount; +static ReplayIcount icount_data; + + +/* Return the virtual CPU time, based on the instruction counter. */ +int64_t replay_get_icount(void) +{ + int64_t icount = replay_get_current_step(); + return icount_data.bias + (icount << replay_icount); +} + +static void replay_icount_warp_rt(void *opaque) +{ + if (icount_data.vm_clock_warp_start == -1) { + return; + } + + if (runstate_is_running()) { + int64_t clock = qemu_clock_get_ns(QEMU_CLOCK_HOST); + int64_t warp_delta = clock - icount_data.vm_clock_warp_start; + icount_data.bias += warp_delta; + if (qemu_clock_expired(QEMU_CLOCK_VIRTUAL)) { + qemu_notify_event(); + } + } + icount_data.vm_clock_warp_start = -1; +} + +void replay_clock_warp(void) +{ + int64_t deadline; + if (!replay_checkpoint(9)) + return; + /* + * If the CPUs have been sleeping, advance the vm_clock timer now. This + * ensures that the deadline for the timer is computed correctly below. + * This also makes sure that the insn counter is synchronized before the + * CPU starts running, in case the CPU is woken by an event other than + * the earliest vm_clock timer. + */ + if (icount_data.vm_clock_warp_start != -1) { + replay_icount_warp_rt(NULL); + } + if (!all_cpu_threads_idle() || !qemu_clock_has_timers(QEMU_CLOCK_VIRTUAL)) { + timer_del(icount_data.icount_warp_timer); + return; + } + + icount_data.vm_clock_warp_start = qemu_clock_get_ns(QEMU_CLOCK_HOST); + deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL); + if (deadline > 0) { + /* + * Ensure the vm_clock proceeds even when the virtual CPU goes to + * sleep. Otherwise, the CPU might be waiting for a future timer + * interrupt to wake it up, but the interrupt never comes because + * the vCPU isn't running any insns and thus doesn't advance the + * vm_clock. + * + * An extreme solution for this problem would be to never let VCPUs + * sleep in icount mode if there is a pending vm_clock timer; rather + * time could just advance to the next vm_clock event. Instead, we + * do stop VCPUs and only advance vm_clock after some "real" time, + * (related to the time left until the next event) has passed. This + * rt_clock timer will do this. This avoids that the warps are too + * visible externally---for example, you will not be sending network + * packets continuously instead of every 100ms. + */ + timer_mod_ns(icount_data.icount_warp_timer, icount_data.vm_clock_warp_start + deadline); + } else { + qemu_notify_event(); + } +} + +static const VMStateDescription vmstate_icount = { + .name = "icount", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_INT64(bias, ReplayIcount), + VMSTATE_TIMER(icount_warp_timer, ReplayIcount), + VMSTATE_INT64(vm_clock_warp_start, ReplayIcount), + VMSTATE_END_OF_LIST() + } +}; + +void replay_configure_icount(void) +{ + vmstate_register(NULL, 0, &vmstate_icount, &icount_data); + icount_data.icount_warp_timer = timer_new_ns(QEMU_CLOCK_HOST, replay_icount_warp_rt, NULL); +} diff --git a/replay/replay-input.c b/replay/replay-input.c new file mode 100644 index 0000000..c4dc7c5 --- /dev/null +++ b/replay/replay-input.c @@ -0,0 +1,152 @@ +#include "replay.h" +#include "replay-internal.h" +#include "qapi-types.h" +#include "sysemu/sysemu.h" +#include "monitor/monitor.h" +#include "ui/input.h" +#include "qemu/log.h" + +void replay_save_input_event(InputEvent *evt) +{ + replay_put_dword(evt->kind); + + switch (evt->kind) + { + case INPUT_EVENT_KIND_KEY: + replay_put_dword(evt->key->key->kind); + + switch (evt->key->key->kind) + { + case KEY_VALUE_KIND_NUMBER: + replay_put_qword(evt->key->key->number); + replay_put_byte(evt->key->down); + break; + case KEY_VALUE_KIND_QCODE: + replay_put_dword(evt->key->key->qcode); + replay_put_byte(evt->key->down); + break; + case KEY_VALUE_KIND_MAX: + /* keep gcc happy */ + break; + } + 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_MAX: + /* keep gcc happy */ + break; + } +} + +InputEvent *replay_read_input_event(void) +{ + static InputEvent evt; + static KeyValue keyValue; + static InputKeyEvent key; + key.key = &keyValue; + static InputBtnEvent btn; + static InputMoveEvent rel; + static InputMoveEvent abs; + + evt.kind = replay_get_dword(); + switch (evt.kind) + { + case INPUT_EVENT_KIND_KEY: + evt.key = &key; + evt.key->key->kind = replay_get_dword(); + + switch (evt.key->key->kind) + { + case KEY_VALUE_KIND_NUMBER: + evt.key->key->number = replay_get_qword(); + evt.key->down = replay_get_byte(); + break; + case KEY_VALUE_KIND_QCODE: + evt.key->key->qcode = (QKeyCode)replay_get_dword(); + evt.key->down = replay_get_byte(); + break; + case KEY_VALUE_KIND_MAX: + /* keep gcc happy */ + break; + } + break; + case INPUT_EVENT_KIND_BTN: + evt.btn = &btn; + evt.btn->button = (InputButton)replay_get_dword(); + evt.btn->down = replay_get_byte(); + break; + case INPUT_EVENT_KIND_REL: + evt.rel = &rel; + evt.rel->axis = (InputAxis)replay_get_dword(); + evt.rel->value = replay_get_qword(); + break; + case INPUT_EVENT_KIND_ABS: + evt.abs = &abs; + evt.abs->axis = (InputAxis)replay_get_dword(); + evt.abs->value = replay_get_qword(); + break; + case INPUT_EVENT_KIND_MAX: + /* keep gcc happy */ + break; + } + + return &evt; +} + +void replay_skip_input_event(struct Monitor *mon, uint64_t step) +{ + switch (replay_get_dword()) + { + case INPUT_EVENT_KIND_KEY: + switch (replay_get_dword()) + { + case KEY_VALUE_KIND_NUMBER: + { + int64_t number = replay_get_qword(); + bool down = replay_get_byte(); + monitor_printf(mon, "%" PRId64 ": Key %"PRIx64" %s\n", step, number, down ? "pressed" : "released"); + } + break; + case KEY_VALUE_KIND_QCODE: + { + int qcode = replay_get_dword(); + bool down = replay_get_byte(); + monitor_printf(mon, "%" PRId64 ": Key %d %s\n", step, qcode, down ? "pressed" : "released"); + } + break; + case KEY_VALUE_KIND_MAX: + /* keep gcc happy */ + break; + } + break; + case INPUT_EVENT_KIND_BTN: + { + int btn = replay_get_dword(); + bool down = replay_get_byte(); + monitor_printf(mon, "%" PRId64 ": Mouse buttons %d %s\n", step, btn, down ? "pressed" : "released"); + } + break; + case INPUT_EVENT_KIND_REL: + replay_get_dword(); + replay_get_qword(); + break; + case INPUT_EVENT_KIND_ABS: + replay_get_dword(); + replay_get_qword(); + break; + case INPUT_EVENT_KIND_MAX: + /* keep gcc happy */ + break; + } +} + diff --git a/replay/replay-internal.c b/replay/replay-internal.c new file mode 100644 index 0000000..96dda4d --- /dev/null +++ b/replay/replay-internal.c @@ -0,0 +1,176 @@ +//#include "buffered_file.h" +#include "qemu-common.h" +#include "exec/cpu-common.h" +#include "qemu/queue.h" +#include "replay-internal.h" + +volatile unsigned int replay_data_kind = -1; +volatile unsigned int replay_has_unread_data; + +/* File for replay writing */ +FILE *replay_file; + +void replay_put_byte(unsigned char byte) +{ + if (replay_file) + { + fwrite(&byte, sizeof(byte), 1, replay_file); + } +} + +void replay_put_event(unsigned char event) +{ + replay_put_byte(event); +} + + +void replay_put_word(uint16_t word) +{ + if (replay_file) + fwrite(&word, sizeof(word), 1, replay_file); +} + +void replay_put_dword(unsigned int dword) +{ + if (replay_file) + fwrite(&dword, sizeof(dword), 1, replay_file); +} + +void replay_put_qword(int64_t qword) +{ + if (replay_file) + fwrite(&qword, sizeof(qword), 1, replay_file); +} + + +void replay_put_array(const uint8_t *buf, size_t size) +{ + if (replay_file) + { + fwrite(&size, sizeof(size), 1, replay_file); + fwrite(buf, 1, size, replay_file); + } +} + + + +unsigned char replay_get_byte(void) +{ + unsigned char byte; + if (replay_file) + { + fread(&byte, sizeof(byte), 1, replay_file); + } + return byte; +} + +uint16_t replay_get_word(void) +{ + uint16_t word; + if (replay_file) + fread(&word, sizeof(word), 1, replay_file); + + return word; +} + +unsigned int replay_get_dword(void) +{ + unsigned int dword; + if (replay_file) + fread(&dword, sizeof(dword), 1, replay_file); + + return dword; +} + +int64_t replay_get_qword(void) +{ + int64_t qword; + if (replay_file) + fread(&qword, sizeof(qword), 1, replay_file); + + return qword; +} + + +void replay_get_array(uint8_t *buf, size_t *size) +{ + if (replay_file) + { + fread(size, sizeof(*size), 1, replay_file); + fread(buf, 1, *size, replay_file); + } +} + +void replay_get_array_alloc(uint8_t **buf, size_t *size) +{ + if (replay_file) + { + fread(size, sizeof(*size), 1, replay_file); + *buf = g_malloc(*size); + fread(*buf, 1, *size, replay_file); + } +} + +void replay_check_error(void) +{ + if (replay_file) + { + if (feof(replay_file)) + { + fprintf(stderr, "replay file is over\n"); + exit(1); + } + else if (ferror(replay_file)) + { + fprintf(stderr, "replay file is over or something goes wrong\n"); + exit(1); + } + } +} + + + +void replay_fetch_data_kind(void) +{ + if (replay_file) + { + if (!replay_has_unread_data) + { + replay_data_kind = replay_get_byte(); + replay_check_error(); + replay_has_unread_data = 1; + } + } +} + +//! Saves cached instructions. +void replay_save_instructions(void) +{ + if (replay_file && replay_mode == REPLAY_SAVE) + { + if (first_cpu != NULL && first_cpu->instructions_count > 0) + { + replay_put_event(EVENT_INSTRUCTION); + replay_put_dword(first_cpu->instructions_count); + replay_state.current_step += first_cpu->instructions_count; + first_cpu->instructions_count = 0; + } +#ifdef _WIN32 + replay_save_sound_in(); + replay_save_sound_out(); +#endif + } +} + + +void validate_data_kind(int kind) +{ + replay_fetch_data_kind(); + if (replay_data_kind != kind) + { + fprintf(stderr, "%" PRId64 ": read data kind %d instead of expected %d\n", + replay_get_current_step(), replay_data_kind, kind); + exit(1); + } +} + diff --git a/replay/replay-internal.h b/replay/replay-internal.h new file mode 100644 index 0000000..10d5734 --- /dev/null +++ b/replay/replay-internal.h @@ -0,0 +1,192 @@ +#ifndef REPLAY_INTERNAL_H +#define REPLAY_INTERNAL_H + +#include + +// internal data for savevm +#define EVENT_END_STARTUP 0 +// for time_t event +#define EVENT_TIME_T 1 +// for tm event +#define EVENT_TM 2 +// for outgoing sound event +#define EVENT_SOUND_OUT 7 +// for incoming sound event +#define EVENT_SOUND_IN 8 +// mouse input events +#define EVENT_INPUT 10 +// for software interrupt +#define EVENT_INTERRUPT 15 +// for debug information +#define EVENT_ASSERT 17 +// for timing information +#define EVENT_TARGET_TIME 19 +// for shutdown request +#define EVENT_SHUTDOWN 20 +// for save VM event +#define EVENT_SAVE_VM_BEGIN 21 +// for save VM event +#define EVENT_SAVE_VM_END 22 +// for emulated exceptions +#define EVENT_EXCEPTION 23 +// for async events +#define EVENT_ASYNC 24 +#define EVENT_ASYNC_OPT 25 +// for int data +#define EVENT_DATA_INT 26 +// for data buffer +#define EVENT_DATA_BUFFER 27 +// for checkoint event +#define EVENT_INSTRUCTION 32 +// for clock read/writes +#define EVENT_CLOCK 64 +// some of grteater codes are reserved for clocks + +// for checkpoint event +#define EVENT_CHECKPOINT 96 +// end of log event +#define EVENT_END 127 + +// limited by Ethernet frame size +#define MAX_NET_PACKET_SIZE 1560 + +/*! Information about saved VM state */ +struct SavedStateInfo +{ + // Offset in the replay log file where state is saved. + uint64_t file_offset; + // Step number, corresponding to the saved state. + uint64_t step; +}; + +struct Monitor; + +typedef struct ReplayState +{ + //! Cached clock values. + int64_t cached_clock[REPLAY_CLOCK_COUNT]; + //! Nonzero, when next instruction is repeated one and was already + //! processed. + int skipping_instruction; + /*! Current step - number of processed instructions and timer events. */ + uint64_t current_step; + + /*! Temporary data for saving/loading replay file position. */ + uint64_t file_offset; +} ReplayState; +extern ReplayState replay_state; + +/*! Reference to the saved state */ +typedef struct SavedStateInfo SavedStateInfo; + +extern volatile unsigned int replay_data_kind; +extern volatile unsigned int replay_has_unread_data; + +extern int play_submode; +extern SavedStateInfo *saved_states; + +/* File for replay writing */ +extern FILE *replay_file; + +void replay_put_byte(unsigned char byte); +void replay_put_event(unsigned char event); +void replay_put_word(uint16_t word); +void replay_put_dword(unsigned int dword); +void replay_put_qword(int64_t qword); +void replay_put_array(const uint8_t *buf, size_t size); + +unsigned char replay_get_byte(void); +uint16_t replay_get_word(void); +unsigned int 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); + +//! Checks error status of the file. +void replay_check_error(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); +//! Checks that the next data is corresponding to the desired kind. +//! Terminates the program in case of error. +void validate_data_kind(int kind); + + +/*! Saves events from queue into the file */ +void replay_save_events(int opt); +/*! Returns true if there are any unsaved events in the queue */ +bool replay_has_events(void); +/*! Read events from the file into the input queue */ +void replay_read_events(int opt); +/*! Initializes events' processing internals */ +void replay_init_events(void); +/*! Clears internal data structures for events handling */ +void replay_finish_events(void); +/*! Skips async event in the log file. + Called by the replay_events function. + If the event is network packet it should be printed to monitor. */ +void replay_skip_async_event(struct Monitor *mon, uint64_t step, bool opt); +/*! Clears events list before loading new VM state */ +void replay_clear_events(void); + +/*! Finds saved state info which is nearest before the specified step. */ +SavedStateInfo *find_nearest_state(uint64_t step); + + +// USB events +void replay_event_usb_ctrl(void *opaque); +void replay_event_usb_data(void *opaque); +void replay_event_usb_iso(void *opaque); +void replay_event_save_usb_xfer(void *opaque); +void replay_event_save_usb_iso_xfer(void *opaque); +void replay_event_read_usb_xfer(void *opaque); +void replay_event_read_usb_iso_xfer(void *opaque); +void replay_event_skip_usb_xfer(void); +void replay_event_skip_usb_iso_xfer(void); +bool replay_usb_has_xfers(void); +void replay_add_usb_event(unsigned int event_id, uint64_t id, void *opaque); + +/*! Called to run char device event. */ +void replay_event_char_run(void *opaque); +/*! Writes char event to the file. */ +void replay_event_char_save(void *opaque); +/*! Reads char event from the file. */ +void *replay_event_char_read(void); +/*! Skips char event. */ +void replay_event_char_skip(void); +/*! Flushes events queue */ +void replay_flush_events(void); + +// icount +void replay_configure_icount(void); + +// input events +void replay_save_input_event(InputEvent *evt); +InputEvent *replay_read_input_event(void); +void replay_skip_input_event(struct Monitor *mon, uint64_t step); + +// sound events +bool replay_has_sound_events(void); +void replay_save_sound_out(void); +void replay_save_sound_in(void); +void replay_read_sound_out(void); +void replay_read_sound_in(void); +void replay_sound_flush_queue(void); + +// Network events +void replay_net_init(void); +void replay_net_free(void); +void replay_net_read_packets_data(void); +void replay_net_write_packets_data(void); +void replay_net_save_packet(void *opaque); +void *replay_net_read_packet(void); +void replay_net_skip_packet(struct Monitor *mon, uint64_t step); +/*! Called to send packet that was read or received from external input + to the net queue. */ +void replay_net_send_packet(void *opaque); + +#endif // REPLAY_INTERNAL_H diff --git a/replay/replay-net.c b/replay/replay-net.c new file mode 100644 index 0000000..6c39713 --- /dev/null +++ b/replay/replay-net.c @@ -0,0 +1,367 @@ +#include "qemu-common.h" +#include "exec/cpu-common.h" +#include "qemu/log.h" +#include "net/net.h" +#include "slirp/slirp.h" +#include "slirp/libslirp.h" +#include "monitor/monitor.h" +#include "replay.h" +#include "replay-internal.h" + +/* Network data */ +NetClientState **vlan_states = NULL; +size_t vlan_states_count = 0; +size_t vlan_states_capacity = 0; + +/* Structure for storing information about the network packet */ +typedef struct +{ + /* Offset in the replay log file where packet is saved. */ + uint64_t file_offset; + /* Number of step when packet came. */ + uint64_t step; +} QEMU_PACKED NetPacketInfo; + +typedef struct NetPacketQueue +{ + /* ID of the packet */ + uint64_t id; + /* ID of the network client */ + int32_t nc_id; + size_t size; + uint8_t buf[MAX_NET_PACKET_SIZE]; + uint64_t offset; +} NetPacketQueue; + +/* Network packets count. */ +static uint64_t net_packets_count; +/* Capacity of the array for packets parameters. */ +static uint64_t net_packets_capacity; +/* Array for storing network packets parameters. */ +static NetPacketInfo *net_packets; + +/* Data for network fuzzing - modified versions of packets */ + +/* Packet data */ +typedef struct ModifiedNetPacketInfo +{ + /* ID of the packet */ + int64_t id; + /* Size of the packet */ + size_t size; + /* Contents of the packet */ + uint8_t *buf; + + QLIST_ENTRY(ModifiedNetPacketInfo) next; +} ModifiedNetPacketInfo; + +static QLIST_HEAD(, ModifiedNetPacketInfo) modified_net_packets = + QLIST_HEAD_INITIALIZER(modified_net_packets); + + +void replay_net_init(void) +{ + net_packets_count = 0; +} + +void replay_net_read_packets_data(void) +{ + // read network packets info + net_packets_count = replay_get_qword(); + net_packets_capacity = net_packets_count; + if (net_packets_count) + { + net_packets = (NetPacketInfo*)g_malloc(sizeof(NetPacketInfo) * net_packets_count); + if (fread(net_packets, sizeof(NetPacketInfo), net_packets_count, replay_file) != net_packets_count) + { + fprintf(stderr, "Internal error in replay_net_read_packets_data\n"); + exit(1); + } + } +} + +void replay_net_write_packets_data(void) +{ + // write network packets info + replay_put_qword(net_packets_count); + if (net_packets && net_packets_count) + { + // TODO ??? Save distinct fields in the loop + fwrite(net_packets, sizeof(NetPacketInfo), net_packets_count, replay_file); + } +} + +void replay_add_network_client(NetClientState *nc) +{ + if (vlan_states_count == 0) + { + vlan_states = (NetClientState **)g_malloc(sizeof(*vlan_states)); + vlan_states_count = 0; + vlan_states_capacity = 1; + } + else if (vlan_states_count == vlan_states_capacity) + { + vlan_states_capacity *= 2; + vlan_states = (NetClientState **)g_realloc(vlan_states, sizeof(*vlan_states) * vlan_states_capacity); + } + + vlan_states[vlan_states_count++] = nc; +} + +void replay_net_free(void) +{ + // free modified packets + ModifiedNetPacketInfo *packet, *nextPacket; + QLIST_FOREACH_SAFE(packet, &modified_net_packets, next, nextPacket) + { + QLIST_REMOVE(packet, next); + g_free(packet->buf); + g_free(packet); + } + + if (vlan_states) + { + g_free(vlan_states); + vlan_states = NULL; + } +} + + +//! Saves network packet into the log. +void replay_save_net_packet(NetClientState *nc, const uint8_t *buf, size_t size) +{ + if (replay_file) + { + // save packet info into the array + if (net_packets_capacity == net_packets_count) + { + if (net_packets_capacity == 0) + net_packets_capacity = 1; + else + net_packets_capacity *= 2; + net_packets = (NetPacketInfo*)g_realloc(net_packets, net_packets_capacity * sizeof(NetPacketInfo)); + } + + // add packet processing event to the queue + NetPacketQueue *p = (NetPacketQueue*)g_malloc0(sizeof(NetPacketQueue)); + p->id = net_packets_count; + p->size = size; + if (net_hub_id_for_client(nc, &p->nc_id) < 0) + { + fprintf(stderr, "Replay: Cannot determine net client id\n"); + exit(1); + } + memcpy(p->buf, buf, size); + replay_add_event(REPLAY_ASYNC_EVENT_NETWORK, p); + + ++net_packets_count; + } +} + + +static ModifiedNetPacketInfo *find_modified_packet(int64_t id) +{ + ModifiedNetPacketInfo *packet = NULL; + QLIST_FOREACH(packet, &modified_net_packets, next) + { + if (packet->id == id) + return packet; + } + + return NULL; +} + +static NetClientState *replay_net_find_vlan(NetPacketQueue *packet) +{ + int i; + for (i = 0 ; i < vlan_states_count ; ++i) + { + int id = 0; + if (net_hub_id_for_client(vlan_states[i], &id) < 0) + { + fprintf(stderr, "Replay: Cannot determine net client id\n"); + exit(1); + } + if (id == packet->nc_id) + return vlan_states[i]; + } + + fprintf(stderr, "Replay: please specify -net replay command-line option\n"); + exit(1); + + return NULL; +} + +void replay_net_send_packet(void *opaque) +{ + NetPacketQueue *packet = (NetPacketQueue *)opaque; + NetClientState *vlan_state = replay_net_find_vlan(packet); + + if (replay_mode == REPLAY_SAVE) + { + net_packets[packet->id].file_offset = packet->offset; + net_packets[packet->id].step = replay_get_current_step(); + + qemu_send_packet(vlan_state, packet->buf, packet->size); + } + else if (replay_mode == REPLAY_PLAY) + { + // fuzzing or not + ModifiedNetPacketInfo *mod_packet = find_modified_packet(packet->id); + if (mod_packet) + { + replay_set_play_changed(); + qemu_send_packet(vlan_state, mod_packet->buf, mod_packet->size); + } + else + { + qemu_send_packet(vlan_state, packet->buf, packet->size); + } + } + + g_free(packet); +} + +int replay_get_packet(struct Monitor *mon, int64_t id) +{ + if (replay_mode == REPLAY_PLAY && replay_file) + { + if (id < 0 || id >= net_packets_count) + return 0; + + uint64_t offset = ftello64(replay_file); + + size_t size; + // find the packet + fseeko64(replay_file, net_packets[id].file_offset, SEEK_SET); + // skip the id and nc_id + fseek(replay_file, sizeof(uint64_t), SEEK_CUR); + fseek(replay_file, sizeof(uint32_t), SEEK_CUR); + + monitor_printf(mon, "Packet: "); + // read the packet + if (fread(&size, sizeof(size), 1, replay_file) != 1) + { + fprintf(stderr, "Internal error in replay_get_packet\n"); + exit(1); + } + while (size--) + { + uint8_t byte; + if (fread(&byte, sizeof(byte), 1, replay_file) != 1) + { + fprintf(stderr, "Internal error in replay_get_packet\n"); + exit(1); + } + //monitor_printf(mon, "%02x", byte); + monitor_print_bytes(mon, &byte, sizeof(byte)); + } + monitor_printf(mon, "#\n\n"); + + // restore original position + fseeko64(replay_file, offset, SEEK_SET); + + //**************************** printing modified packet + ModifiedNetPacketInfo *packet = find_modified_packet(id); + if (packet != NULL) + { + monitor_printf(mon, "Modified packet: "); + size_t i = 0; + for ( ; i < packet->size ; ++i) + monitor_print_bytes(mon, &packet->buf[i], sizeof(uint8_t)); + + monitor_printf(mon, "#\n\n"); + } + //**************************** + + return 1; + } + + return 0; +} + + +int replay_set_packet(struct Monitor *mon, int64_t id, const char *bytes) +{ + if (replay_mode == REPLAY_PLAY && replay_file) + { + if (bytes == NULL) + { + // remove packet + ModifiedNetPacketInfo *packet = find_modified_packet(id); + if (packet == NULL) + return 0; + + QLIST_REMOVE(packet, next); + g_free(packet->buf); + g_free(packet); + + return 1; + } + else + { + ModifiedNetPacketInfo *packet = find_modified_packet(id); + if (!packet) + { + // add packet + packet = (ModifiedNetPacketInfo *)g_malloc(sizeof(*packet)); + QLIST_INSERT_HEAD(&modified_net_packets, packet, next); + packet->id = id; + } + else + { + g_free(packet->buf); + } + + packet->size = strlen(bytes) / 2; + packet->buf = g_malloc(packet->size); + size_t i = 0; + for ( ; i < packet->size ; ++i) + { + packet->buf[i] = replay_xtoi(bytes[2 * i]) * 16 + replay_xtoi(bytes[2 * i + 1]); + } + + return 1; + } + } + + return 0; +} + +void replay_net_save_packet(void *opaque) +{ + NetPacketQueue *p = (NetPacketQueue*)opaque; + p->offset = ftello64(replay_file); + replay_put_qword(p->id); + replay_put_dword(p->nc_id); + replay_put_array(p->buf, p->size); + replay_network_packets += p->size; +} + +void *replay_net_read_packet(void) +{ + NetPacketQueue *p = g_malloc0(sizeof(NetPacketQueue));; + p->id = replay_get_qword(); + p->nc_id = replay_get_dword(); + replay_get_array(p->buf, &p->size); + replay_check_error(); + + return p; +} + +void replay_net_skip_packet(struct Monitor *mon, uint64_t step) +{ + uint64_t id; + int nc_id; + size_t size; + if (fread(&id, sizeof(id), 1, replay_file) != 1 + || fread(&nc_id, sizeof(nc_id), 1, replay_file) != 1 + || fread(&size, sizeof(size), 1, replay_file) != 1) + { + fprintf(stderr, "Internal error while skipping network packet\n"); + exit(1); + } + fseek(replay_file, size, SEEK_CUR); + monitor_printf(mon, "%" PRId64 ": Net packet net=%d id=%" PRId64 "\n", step, nc_id, id); +} + diff --git a/replay/replay-usb.c b/replay/replay-usb.c new file mode 100644 index 0000000..9ee96e8 --- /dev/null +++ b/replay/replay-usb.c @@ -0,0 +1,241 @@ +#include "qemu-common.h" +#include "replay.h" +#include "replay-internal.h" +#include "hw/usb.h" + +#ifdef CONFIG_USB_LIBUSB +#include "hw/host-libusb.h" + +static uint64_t replay_get_xfer_id(struct libusb_transfer *xfer) +{ + USBHostRequest *r = xfer->user_data; + USBHostDevice *host = r->host; + + return ((uint64_t)host->match.vendor_id << 32) + | host->match.product_id; +} + +static uint64_t replay_get_iso_xfer_id(struct libusb_transfer *xfer) +{ + USBHostIsoXfer *r = xfer->user_data; + USBHostDevice *host = r->ring->host; + + return ((uint64_t)host->match.vendor_id << 32) + | host->match.product_id; +} + +void replay_req_complete_ctrl(struct libusb_transfer *xfer) +{ + if (replay_mode == REPLAY_SAVE) + { + replay_add_usb_event(REPLAY_ASYNC_EVENT_USB_CTRL, + replay_get_xfer_id(xfer), xfer); + } +} + +void replay_req_register_ctrl(struct libusb_transfer *xfer) +{ + if (replay_mode == REPLAY_PLAY) + { + replay_add_usb_event(REPLAY_ASYNC_EVENT_USB_CTRL, + replay_get_xfer_id(xfer), xfer); + } +} + + +void replay_event_usb_ctrl(void *opaque) +{ + struct libusb_transfer *xfer = opaque; + + usb_host_req_complete_ctrl(xfer); +} + +void replay_event_save_usb_xfer(void *opaque) +{ + struct libusb_transfer *xfer = opaque; + USBHostRequest *r = xfer->user_data; + if (replay_mode == REPLAY_SAVE) + { + // save data + replay_put_dword(xfer->status); + replay_put_dword(xfer->actual_length); + replay_put_array(xfer->buffer, r->in ? xfer->length : 0); + } +} + +void replay_event_save_usb_iso_xfer(void *opaque) +{ + struct libusb_transfer *xfer = opaque; + USBHostIsoXfer *iso = xfer->user_data; + int i; + if (replay_mode == REPLAY_SAVE) + { + bool in = iso->ring->ep->pid == USB_TOKEN_IN; + // save data + replay_put_dword(xfer->status); + replay_put_dword(xfer->num_iso_packets); + for (i = 0 ; i < xfer->num_iso_packets ; ++i) + { + // all other fields of the packet are not used + unsigned int len = xfer->iso_packet_desc[i].actual_length; + //replay_put_dword(len); + if (/*len && */in) + { + replay_put_array(usb_host_get_iso_packet_buffer(iso, i), len); + } + } + } +} + +void replay_event_read_usb_xfer(void *opaque) +{ + struct libusb_transfer *xfer = opaque; + USBHostRequest *r = xfer->user_data; + + if (replay_mode == REPLAY_PLAY) + { + // read data here + xfer->status = replay_get_dword(); + xfer->actual_length = replay_get_dword(); + size_t sz; + replay_get_array(xfer->buffer, &sz); + if (r->in && xfer->length != (int)sz) + { + fprintf(stderr, "Replay: trying to read USB control/data buffer with unexpected size\n"); + exit(1); + } + } +} + +void replay_event_read_usb_iso_xfer(void *opaque) +{ + struct libusb_transfer *xfer = opaque; + USBHostIsoXfer *iso = xfer->user_data; + int i; + + if (replay_mode == REPLAY_PLAY) + { + bool in = iso->ring->ep->pid == USB_TOKEN_IN; + // read data here + xfer->status = replay_get_dword(); + xfer->num_iso_packets = replay_get_dword(); + for (i = 0 ; i < xfer->num_iso_packets ; ++i) + { + // all other fields of the packet are not used + //unsigned int len = replay_get_dword(); + if (in) + { + size_t sz; + replay_get_array(usb_host_get_iso_packet_buffer(iso, i), &sz); + xfer->iso_packet_desc[i].actual_length = (unsigned int)sz; + } + } + } +} + +void replay_event_skip_usb_xfer(void) +{ + if (replay_mode == REPLAY_PLAY) + { + size_t sz; + // data/ctrl + replay_get_dword(); + replay_get_dword(); + if (fread(&sz, sizeof(sz), 1, replay_file) != 1) + { + fprintf(stderr, "Internal error while skipping usb xfer\n"); + exit(1); + } + fseek(replay_file, sz, SEEK_CUR); + } +} + +void replay_event_skip_usb_iso_xfer(void) +{ + if (replay_mode == REPLAY_PLAY) + { + // status + replay_get_dword(); + // count + int num = replay_get_dword(); + // packets + while (num--) + { + size_t sz; + if (fread(&sz, sizeof(sz), 1, replay_file) != 1) + { + fprintf(stderr, "Internal error while skipping usb iso xfer\n"); + exit(1); + } + fseek(replay_file, sz, SEEK_CUR); + } + } +} + + +void replay_req_complete_data(struct libusb_transfer *xfer) +{ + if (replay_mode == REPLAY_SAVE) + { + replay_add_usb_event(REPLAY_ASYNC_EVENT_USB_DATA, + replay_get_xfer_id(xfer), xfer); + } +} + +void replay_req_register_data(struct libusb_transfer *xfer) +{ + if (replay_mode == REPLAY_PLAY) + { + replay_add_usb_event(REPLAY_ASYNC_EVENT_USB_DATA, + replay_get_xfer_id(xfer), xfer); + } +} + + +void replay_event_usb_data(void *opaque) +{ + struct libusb_transfer *xfer = opaque; + + usb_host_req_complete_data(xfer); +} + + +void replay_req_complete_iso(struct libusb_transfer *xfer) +{ + if (replay_mode == REPLAY_SAVE) + { + replay_add_usb_event(REPLAY_ASYNC_EVENT_USB_ISO, + replay_get_iso_xfer_id(xfer), xfer); + } +} + +void replay_req_register_iso(struct libusb_transfer *xfer) +{ + if (replay_mode == REPLAY_PLAY) + { + USBHostIsoXfer *r = xfer->user_data; + USBHostDevice *s = r->ring->host; + + replay_add_usb_event(REPLAY_ASYNC_EVENT_USB_ISO, + replay_get_iso_xfer_id(xfer), xfer); + } +} + + +void replay_event_usb_iso(void *opaque) +{ + struct libusb_transfer *xfer = opaque; + + usb_host_req_complete_iso(xfer); +} + +#endif + +bool replay_usb_has_xfers(void) +{ +#ifdef CONFIG_USB_LIBUSB + return usb_host_has_xfers(); +#else + return false; +#endif +} diff --git a/replay/replay.c b/replay/replay.c new file mode 100644 index 0000000..6897c88 --- /dev/null +++ b/replay/replay.c @@ -0,0 +1,1604 @@ +#include +#include +#include +#include + +#include "qemu-common.h" +#include "exec/cpu-common.h" + +#include "replay.h" +#include "replay-internal.h" + +#include "exec/cpu-defs.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "sysemu/sysemu.h" +#include "ui/console.h" +#include "monitor/monitor.h" +#include "ui/keymaps.h" +#include "exec/exec-all.h" +#include "qapi/qmp/qlist.h" +#include "qemu/thread.h" +#include "sysemu/char.h" +#include "cpu.h" +#include "hw/ide/internal.h" +#include "hw/ide/pci.h" +#include "block/block_int.h" +#include "sysemu/blockdev.h" + +// Current version of the replay mechanism. +// Increase it when file format changes. +#define REPLAY_VERSION 0xe02001 +// Size of replay log header +#define HEADER_SIZE (sizeof(uint32_t) + sizeof(uint64_t)) + +int replay_mode = REPLAY_NONE; +/*! Stores current submode for PLAY mode */ +int play_submode = REPLAY_PLAY_UNKNOWN; + +uint64_t replay_network_packets; + +ReplayState replay_state; + +/*! Step for stopping execution at. */ +static uint64_t replay_break_step = -1; +/*! Non-zero, when debug events should be written */ +static int debug_mode = 0; + +/* Offsets of clocks used for switching from replay to execution */ +int64_t realtime_clock_replay_offset; +int64_t real_ticks_replay_offset; + +/* Name of replay file */ +static char *replay_filename; +/* Suffix for the disk images filenames */ +char *replay_image_suffix; + +int not_compatible_replay_param = 0; + +/* + Auto-saving for VM states data +*/ + +/* Minimum capacity of saved states information array */ +#define SAVED_STATES_MIN_CAPACITY 128 +/* Format of the name for the saved state */ +#define SAVED_STATE_NAME_FORMAT "replay_%" PRId64 + +/* Timer for auto-save VM states */ +static QEMUTimer *save_timer; +/* Save state period in seconds */ +static uint64_t save_state_period; +/* List of the saved states information */ +SavedStateInfo *saved_states; +/* Number of saved states */ +static size_t saved_states_count; +/* Capacity of the buffer for saved states */ +static size_t saved_states_capacity; +/* Number of last loaded/saved state */ +static uint64_t current_saved_state; + +//* Target time */ + +/* Timer for auto-save target RTC */ +static QEMUTimer *target_timer; +/* Target timer saving period */ +static uint64_t target_timer_period; +/* Additional parameter for function which provides time */ +static void *replay_target_time_dev; +/* Pointer to function which provides time */ +static replay_get_date_func replay_get_target_time; + +/***************************************************************************** + * Replay functions + *****************************************************************************/ + +int replay_get_play_submode(void) +{ + return play_submode; +} + + +void replay_set_play_changed(void) +{ + if (replay_mode == REPLAY_PLAY) + { + replay_change(NULL); + } +} + + +static void replay_pre_save(void *opaque) +{ + ReplayState *state = opaque; + state->file_offset = ftello64(replay_file); +} + + +static int replay_post_load(void *opaque, int version_id) +{ + first_cpu->instructions_count = 0; + + // seek the replay file + ReplayState *state = opaque; + fseeko64(replay_file, state->file_offset, SEEK_SET); + replay_has_unread_data = 0; + + return 0; +} + + +static const VMStateDescription vmstate_replay = { + .name = "replay", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .pre_save = replay_pre_save, + .post_load = replay_post_load, + .fields = (VMStateField[]) { + VMSTATE_INT64_ARRAY(cached_clock, ReplayState, REPLAY_CLOCK_COUNT), + VMSTATE_INT32(skipping_instruction, ReplayState), + VMSTATE_UINT64(current_step, ReplayState), + VMSTATE_UINT64(file_offset, ReplayState), + VMSTATE_END_OF_LIST() + } +}; + + +static void replay_savevm(void *opaque) +{ + char name[128]; + uint64_t offset; + +#ifdef CONFIG_USB_LIBUSB + if (replay_usb_has_xfers()) + { + // Retry of saving VM state, when USB host controller is not ready. + // We cannot save or interrupt non-finished transfers, so + // just wait for finishing them later. + timer_mod(save_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1); + return; + } +#endif + + offset = ftello64(replay_file); + + replay_save_instructions(); + + replay_put_event(EVENT_SAVE_VM_BEGIN); + + vm_stop(RUN_STATE_SAVE_VM); + + // save VM state + sprintf(name, SAVED_STATE_NAME_FORMAT, current_saved_state); + if (save_vmstate(default_mon, name) > 0) + { + // if period is 0, save only once + if (save_state_period != 0) + { + timer_mod(save_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + save_state_period); + } + + // add more memory to buffer + if (saved_states_count >= saved_states_capacity) + { + saved_states_capacity += SAVED_STATES_MIN_CAPACITY; + saved_states = g_realloc(saved_states, saved_states_capacity * sizeof(SavedStateInfo)); + if (!saved_states) + { + saved_states_count = 0; + fprintf(stderr, "Replay: Saved states memory reallocation failed.\n"); + exit(1); + } + } + // save state ID into the buffer + saved_states[saved_states_count].file_offset = offset; + saved_states[saved_states_count].step = replay_get_current_step(); + ++saved_states_count; + ++current_saved_state; + } + else + { + fprintf(stderr, "Cannot save simulator states for replay.\n"); + } + + replay_put_event(EVENT_SAVE_VM_END); + + tb_flush_all(); + + vm_start(); +} + + +static void replay_save_target_timer(void *opaque) +{ + struct tm datetime; + + if (replay_mode != REPLAY_SAVE + || !replay_get_target_time) + { + return; + } + + // get time + replay_get_target_time(replay_target_time_dev, &datetime); + + replay_put_event(EVENT_TARGET_TIME); + replay_put_dword(datetime.tm_sec); + replay_put_dword(datetime.tm_min); + replay_put_dword(datetime.tm_hour); + replay_put_dword(datetime.tm_mday); + replay_put_dword(datetime.tm_mon); + replay_put_dword(datetime.tm_year); + + time_t ticks = time(NULL); + struct tm *host = localtime(&ticks); + replay_put_dword(host->tm_sec); + replay_put_dword(host->tm_min); + replay_put_dword(host->tm_hour); + replay_put_dword(host->tm_mday); + replay_put_dword(host->tm_mon + 1); + replay_put_dword(host->tm_year + 1900); + + timer_mod(target_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + target_timer_period); +} + +unsigned long long prev_time = 0; + +static void replay_enable(const char *fname, int mode) +{ + const char *fmode = NULL; + if (replay_file) + { + fprintf(stderr, "some replay operation is already started\n"); + return; + } + + if (use_icount == 2) { + fprintf(stderr, "replay: icount auto mode is not tested yet\n"); + exit(1); + } + + switch (mode) + { + case REPLAY_SAVE: + fmode = "wb"; + break; + case REPLAY_PLAY: + fmode = "rb"; + play_submode = REPLAY_PLAY_NORMAL; + break; + default: + fprintf(stderr, "internal error: invalid replay mode\n"); + exit(1); + } + + atexit(replay_finish); + + replay_file = fopen(fname, fmode); + if (replay_file == NULL) + { + fprintf(stderr, "open %s: %s\n", fname, strerror(errno)); + exit(1); + } + + replay_filename = g_strdup(fname); + + // init variables + replay_mode = mode; + replay_has_unread_data = 0; + replay_data_kind = -1; + replay_state.skipping_instruction = 0; + replay_state.current_step = 0; + replay_break_step = -1; + current_saved_state = 0; + + replay_net_init(); + + // skip file header for SAVE and check it for PLAY + if (replay_mode == REPLAY_SAVE) + { + fseek(replay_file, HEADER_SIZE, SEEK_SET); + } + else if (replay_mode == REPLAY_PLAY) + { + unsigned int version = replay_get_dword(); + uint64_t offset = replay_get_qword(); + if (version != REPLAY_VERSION) + { + fprintf(stderr, "Replay: invalid input log file version\n"); + exit(1); + } + // read states table + fseeko64(replay_file, offset, SEEK_SET); + saved_states_count = replay_get_qword(); + saved_states_capacity = saved_states_count; + if (saved_states_count) + { + saved_states = g_malloc(sizeof(SavedStateInfo) * saved_states_count); + fread(saved_states, sizeof(SavedStateInfo), saved_states_count, replay_file); + } + replay_net_read_packets_data(); + + // go to the beginning + fseek(replay_file, 12, SEEK_SET); + } + + replay_init_events(); + + vmstate_register(NULL, 0, &vmstate_replay, &replay_state); + + prev_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); +} + + +void replay_configure(QemuOpts *opts, int mode) +{ + const char *fname; + + fname = qemu_opt_get(opts, "fname"); + if (!fname) + { + fprintf(stderr, "File name not specified for replay\n"); + exit(1); + } + + const char *suffix = qemu_opt_get(opts, "suffix"); + if (suffix) + { + replay_image_suffix = g_strdup(suffix); + } + else + { + replay_image_suffix = g_strdup("replay_qcow"); + } + + save_state_period = 1000LL * qemu_opt_get_number(opts, "period", 0); + debug_mode = qemu_opt_get_bool(opts, "debug", 0); + + replay_icount = (int)qemu_opt_get_number(opts, "icount", 0); + if (replay_icount) + { + replay_configure_icount(); + } + + if (mode == REPLAY_SAVE) + { + target_timer_period = 1000LL * qemu_opt_get_number(opts, "rtc", 0); + } + + replay_enable(fname, mode); +} + + +void replay_init_timer(void) +{ + replay_enable_events(); + + /* create timer for states auto-saving */ + if (replay_mode == REPLAY_SAVE) + { + saved_states_count = 0; + if (!saved_states) + { + saved_states = g_malloc(sizeof(SavedStateInfo) * SAVED_STATES_MIN_CAPACITY); + saved_states_capacity = SAVED_STATES_MIN_CAPACITY; + } + if (save_state_period) + { + save_timer = timer_new_ms(QEMU_CLOCK_REALTIME, replay_savevm, NULL); + timer_mod(save_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME)); + } + replay_put_event(EVENT_END_STARTUP); + /* Save it right now without waiting for timer */ + replay_savevm(NULL); + + // periodic saving of target time into the log + if (target_timer_period) + { + target_timer = timer_new_ms(QEMU_CLOCK_REALTIME, replay_save_target_timer, NULL); + timer_mod(target_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME)); + } + } + else if (replay_mode == REPLAY_PLAY) + { + // load VM state by seeking to step 0 + replay_state.current_step = -1; + replay_seek_step(0); + replay_set_break(-1); + } +} + + +void replay_change(struct Monitor *mon) +{ + if (play_submode == REPLAY_PLAY_NORMAL) + { + // cannot use breakpoints in this mode + replay_break_step = -1; + // init clock offsets to make them smooth + realtime_clock_replay_offset = get_clock_realtime() - get_clock_realtime_impl(); + real_ticks_replay_offset = cpu_get_real_ticks() - cpu_get_real_ticks_impl(); + // switch play mode + tb_flush_all(); + play_submode = REPLAY_PLAY_CHANGED; + replay_flush_events(); + replay_sound_flush_queue(); + } + else if (play_submode == REPLAY_PLAY_CHANGED) + { + if (mon) + monitor_printf(mon, "Execute replay_seek to the specified state before starting replay_change.\n"); + return; + } + else + { + fprintf(stderr, "Cannot start replay_change\n"); + exit(1); + } +} + + + +//! Closes replay file. +void replay_finish(void) +{ + if (replay_mode == REPLAY_NONE) + return; + replay_save_instructions(); + + // finalize the file + if (replay_file) + { + if (replay_mode == REPLAY_SAVE) + { + uint64_t offset; + // write end event + replay_put_event(EVENT_END); + + // write states table + offset = ftello64(replay_file); + replay_put_qword(saved_states_count); + if (saved_states && saved_states_count) + { + // TODO ??? Save distinct fields in the loop + fwrite(saved_states, sizeof(SavedStateInfo), saved_states_count, replay_file); + } + replay_net_write_packets_data(); + + // write header + fseek(replay_file, 0, SEEK_SET); + replay_put_dword(REPLAY_VERSION); + replay_put_qword(offset); + } + + // close file + fclose(replay_file); + replay_file = NULL; + } + + replay_net_free(); + + // deallocate everything + if (save_timer) + { + timer_del(save_timer); + timer_free(save_timer); + save_timer = NULL; + } + if (target_timer) + { + timer_del(target_timer); + timer_free(target_timer); + target_timer = NULL; + } + if (saved_states) + { + g_free(saved_states); + saved_states = NULL; + } + + if (replay_filename) + { + g_free(replay_filename); + replay_filename = NULL; + } + + if (replay_image_suffix) + { + g_free(replay_image_suffix); + replay_image_suffix = NULL; + } + + replay_finish_events(); +} + + + +//! Saves the value of the desired clock into the output file. +void replay_save_clock(unsigned int kind, int64_t clock) +{ + replay_save_instructions(); + + if (kind >= REPLAY_CLOCK_COUNT) + { + fprintf(stderr, "invalid clock ID %d for replay\n", kind); + exit(1); + } + + if (replay_file) + { + replay_put_event(EVENT_CLOCK + kind); + replay_put_qword(clock); + } +} + + +//! Reads next clock value from the file. +//! If clock kind read from the file is different from the parameter, +//! the value is not used. +//! If the parameter is -1, the clock value is read to the cache anyway. +static void replay_read_next_clock(unsigned int kind) +{ + replay_fetch_data_kind(); + if (replay_file) + { + unsigned int read_kind = replay_data_kind - EVENT_CLOCK; + + if (kind != -1 && read_kind != kind) + { + return; + } + if (read_kind >= REPLAY_CLOCK_COUNT) + { + fprintf(stderr, "invalid clock ID %d was read from replay\n", read_kind); + exit(1); + } + + int64_t clock = replay_get_qword(); + + replay_check_error(); + replay_has_unread_data = 0; + + replay_state.cached_clock[read_kind] = clock; + } +} + + +//! Checks SAVEVM event while reading event log. +static void check_savevm(void) +{ + replay_fetch_data_kind(); + if (replay_data_kind != EVENT_SAVE_VM_BEGIN && replay_data_kind != EVENT_SAVE_VM_END) + { + fprintf(stderr, "read wrong data kind %d within savevm\n", replay_data_kind); + exit(1); + } + replay_has_unread_data = 0; +} + +//! Skips clock events saved to file while saving the VM state. +static void replay_skip_savevm(void) +{ + char name[128]; + + replay_has_unread_data = 0; + + if (runstate_is_running()) + { + vm_stop(RUN_STATE_RESTORE_VM); + } + else + { + // Read saved timers from event log + cpu_disable_ticks(); + // Read checkpoints + while (replay_checkpoint(8)) { + /* Nothing */ + } + } + + ++current_saved_state; + sprintf(name, SAVED_STATE_NAME_FORMAT, current_saved_state); + load_vmstate(name); + + // check the closing event + check_savevm(); + + tb_flush_all(); + + // Read saved timers from event log + cpu_enable_ticks(); + + vm_start(); +} + +//! Skips async events until some sync event will be found. +static int skip_async_events(int stop_event) +{ + /* nothing to skip - not all instructions used + or timer is not processed yet*/ + if (first_cpu != NULL && first_cpu->instructions_count != 0 && replay_has_unread_data) + { + return 0; + } + + int skipping = 1; + while (skipping) + { + replay_fetch_data_kind(); + if (stop_event == replay_data_kind) + skipping = 0; + switch (replay_data_kind) + { + case EVENT_END: + fprintf(stderr, "Replay log file was successfully played\n"); + exit(1); + break; + case EVENT_SOUND_OUT: +#ifdef _WIN32 + replay_read_sound_out(); +#endif + break; + case EVENT_SOUND_IN: +#ifdef _WIN32 + replay_read_sound_in(); +#endif + break; + case EVENT_SAVE_VM_BEGIN: + // cannot correctly load VM while in CPU thread + if (qemu_in_vcpu_thread()) + { + skipping = 0; + break; + } + replay_skip_savevm(); + break; + case EVENT_SHUTDOWN: + replay_has_unread_data = 0; + qemu_system_shutdown_request_impl(); + break; + case EVENT_TARGET_TIME: + replay_has_unread_data = 0; + // target time + replay_get_dword(); + replay_get_dword(); + replay_get_dword(); + replay_get_dword(); + replay_get_dword(); + replay_get_dword(); + // host time + replay_get_dword(); + replay_get_dword(); + replay_get_dword(); + replay_get_dword(); + replay_get_dword(); + replay_get_dword(); + break; + case EVENT_INSTRUCTION: + first_cpu->instructions_count = replay_get_dword(); + skipping = 0; + break; + default: + // clock, time_t, checkpoint and other events + skipping = 0; + break; + } + } + + return 0; +} + + + +//! Skips async events invocations from the input, +//! until required data kind is found. +static int skip_async_events_until(unsigned int kind) +{ + while (true) + { + if (skip_async_events(kind)) + { + return 1; + } + + if (replay_data_kind != kind) + { + // TODO: fix this + if (replay_data_kind == EVENT_ASYNC && kind == EVENT_INSTRUCTION) + { + return 0; + } + + fprintf(stderr, "%" PRId64 ": read data kind %d instead of expected %d\n", + replay_get_current_step(), replay_data_kind, kind); + exit(1); + } + break; + } + + return 0; +} + + +void replay_assert(uint64_t value, const char *message) +{ + if (!replay_file || !debug_mode) + return; + + if (replay_mode == REPLAY_SAVE) + { + replay_save_instructions(); + + replay_put_event(EVENT_ASSERT); + replay_put_qword(value); + } + else if (replay_mode == REPLAY_PLAY) + { + if (play_submode == REPLAY_PLAY_CHANGED) + { + fprintf(stderr, "Cannot read log in replay run mode\n"); + exit(1); + } + + if (skip_async_events_until(EVENT_ASSERT)) + { + // No assert was written here + // TODO: do not exit? + fprintf(stderr, "Cannot read assert\n"); + exit(1); + } + + int64_t val = replay_get_qword(); + + replay_check_error(); + replay_has_unread_data = 0; + + if (value != val) + { + fprintf(stderr, "%" PRId64 ": invalid assert: read %" PRIx64 " instead of expected %" PRIx64 " (%s)\n", + replay_get_current_step(), val, value, message ? message : "---"); + exit(1); + } + } +} + + + +//! Reads next clock event from the input. +int64_t replay_read_clock(unsigned int kind) +{ + if (play_submode == REPLAY_PLAY_CHANGED) + { + fprintf(stderr, "Cannot read log in replay run mode\n"); + exit(1); + } + + if (kind >= REPLAY_CLOCK_COUNT) + { + fprintf(stderr, "invalid clock ID %d for replay\n", kind); + exit(1); + } + + if (replay_file) + { + if (!skip_async_events(EVENT_CLOCK + kind)) + { + if (replay_data_kind == EVENT_CLOCK + kind) + { + replay_read_next_clock(kind); + } + } + int64_t ret = replay_state.cached_clock[kind]; + + return ret; + } + + fprintf(stderr, "REPLAY INTERNAL ERROR %d\n", __LINE__); + exit(1); +} + +int replay_has_timer_request(void) +{ + if (replay_state.skipping_instruction) + { + return 0; + } + + if (replay_mode == REPLAY_PLAY) + { + if (play_submode == REPLAY_PLAY_CHANGED) + return 1; + + skip_async_events(EVENT_ASYNC); + if (replay_data_kind == EVENT_ASYNC) + return 1; + + { + if (replay_get_current_step() == replay_break_step) + return 1; + + return 0; + } + } + else if (replay_mode == REPLAY_SAVE) + { + if (replay_has_events()) + { + return 1; + } + } + + return 0; +} + +void replay_write_interrupt_request(void) +{ + replay_save_instructions(); + replay_put_event(EVENT_INTERRUPT); +} + + +int replay_read_interrupt_request(void) +{ + // try to read event + if (replay_mode == REPLAY_PLAY) + { + skip_async_events(EVENT_INTERRUPT); + if (replay_data_kind == EVENT_INTERRUPT) + { + } + } + int ret = replay_data_kind == EVENT_INTERRUPT; + + return ret; +} + +void replay_reset_interrupt_request(void) +{ + if (replay_data_kind == EVENT_INTERRUPT) + { + replay_has_unread_data = 0; + } +} + +int replay_interrupt_request(void) +{ + if (replay_mode == REPLAY_SAVE) + { + replay_write_interrupt_request(); + return 1; + } + else if (replay_mode == REPLAY_PLAY) + { + if (play_submode == REPLAY_PLAY_CHANGED) + { + fprintf(stderr, "Cannot read log in replay run mode\n"); + exit(1); + } + + if (replay_read_interrupt_request()) + { + replay_reset_interrupt_request(); + return 1; + } + else + { + // no action + // wait here in outer loop + } + + return 0; + } + + return 1; +} + + +bool replay_exception(void) +{ + if (replay_mode == REPLAY_SAVE) + { + replay_save_instructions(); + replay_put_event(EVENT_EXCEPTION); + return true; + } + else if (replay_mode == REPLAY_PLAY) + { + if (play_submode == REPLAY_PLAY_CHANGED) + return true; + + skip_async_events(EVENT_EXCEPTION); + if (replay_data_kind == EVENT_EXCEPTION) + { + replay_has_unread_data = 0; + return true; + } + return false; + } + + return true; +} + + + +void replay_shutdown_request(void) +{ + if (replay_mode == REPLAY_SAVE) + { + replay_put_event(EVENT_SHUTDOWN); + } +} + +bool replay_has_code(void) +{ + if (play_submode == REPLAY_PLAY_CHANGED) + return true; + + if (replay_has_instruction()) + return true; + + if (replay_mode == REPLAY_PLAY) + { + skip_async_events(EVENT_INTERRUPT); + if (replay_data_kind == EVENT_INTERRUPT) + return true; + skip_async_events(EVENT_EXCEPTION); + if (replay_data_kind == EVENT_EXCEPTION) + return true; + return false; + } + + return true; +} + +int replay_has_instruction(void) +{ + int res = 1; + if (replay_state.skipping_instruction) + { + return 1; + } + + if (replay_mode == REPLAY_PLAY) + { + skip_async_events(EVENT_INSTRUCTION); + if (replay_data_kind != EVENT_INSTRUCTION + && replay_data_kind != EVENT_ASYNC) + { + res = 0; + } + } + return res; +} + + +void replay_instruction(int process_timer) +{ + if (replay_state.skipping_instruction) { + replay_state.skipping_instruction = 0; + + return; + } + + if (replay_file) + { + if (replay_mode == REPLAY_SAVE) + { + if (replay_has_sound_events()) + { + replay_save_instructions(); + } + + if (process_timer && replay_has_events()) + { + replay_save_instructions(); + // events will be after the last instruction + replay_save_events(-1); + } + else + { + // instruction - increase the step counter + ++first_cpu->instructions_count; + } + } + else if (replay_mode == REPLAY_PLAY) + { + if (play_submode == REPLAY_PLAY_CHANGED) + { + replay_state.current_step += first_cpu->instructions_count; + first_cpu->instructions_count = 0; + return; + } + + if (skip_async_events_until(EVENT_INSTRUCTION)) + { + // execution should be stopped in changed replay mode + return; + } + // No timer is expected right now + if (first_cpu->instructions_count >= 1) + { + // stop only at instruction + if (replay_get_current_step() == replay_break_step) + { + replay_break_step = -1; + + // for stopping VM + if (play_submode == REPLAY_PLAY_NORMAL) + { + first_cpu->exception_index = EXCP_DEBUG; + monitor_printf(default_mon, "Execution has stopped.\n"); + vm_stop(EXCP_DEBUG); + } + else if (play_submode == REPLAY_PLAY_GOTO) + { + play_submode = REPLAY_PLAY_NORMAL; + cpu_handle_debug_exception(first_cpu->env_ptr); + } + else if (play_submode == REPLAY_PLAY_REVERSE) + { + cpu_handle_debug_exception(first_cpu->env_ptr); + return; + } + // for breaking execution loop + cpu_exit(first_cpu); + return; + } + + ++replay_state.current_step; + --first_cpu->instructions_count; + if (first_cpu->instructions_count == 0) + replay_has_unread_data = 0; + } + else + { + replay_read_events(-1); + } + } + } +} + + +bool replay_has_checkpoint(unsigned int checkpoint) +{ + if (replay_file && replay_mode == REPLAY_PLAY) + { + if (play_submode == REPLAY_PLAY_CHANGED) + return true; + + if (skip_async_events(EVENT_CHECKPOINT + checkpoint)) + { + return false; + } + if (replay_data_kind == EVENT_CHECKPOINT + checkpoint) + { + return true; + } + // not checking the checkpoint id + // assume that this function is called at the right place + if (replay_data_kind == EVENT_ASYNC_OPT) + { + return true; + } + } + + return false; +} + +//! Should be called at optional check points in the execution. +//! These check points are skipped, if they were not meet. +//! Saves checkpoint in the SAVE mode and validates (skipping +//! the interrupts) in the PLAY mode. +//! Returns 0 in PLAY mode if checkpoint was not found. +//! Returns 1 in all other cases. +//! Used checkpoints: 0 1 2 3 5 6 7 8 9 +int replay_checkpoint(unsigned int checkpoint) +{ + replay_save_instructions(); + + if (replay_file) + { + if (replay_mode == REPLAY_PLAY) + { + if (play_submode == REPLAY_PLAY_CHANGED) + return 1; + if (skip_async_events(EVENT_CHECKPOINT + checkpoint)) + { + return 0; + } + if (replay_data_kind != EVENT_CHECKPOINT + checkpoint) + { + if (replay_data_kind == EVENT_ASYNC_OPT) + { + replay_read_events(checkpoint); + replay_fetch_data_kind(); + return replay_data_kind != EVENT_ASYNC_OPT; + } + return 0; + } + replay_has_unread_data = 0; + replay_read_events(checkpoint); + replay_fetch_data_kind(); + return replay_data_kind != EVENT_ASYNC_OPT; + } + else if (replay_mode == REPLAY_SAVE) + { + replay_put_event(EVENT_CHECKPOINT + checkpoint); + replay_save_events(checkpoint); + } + } + + return 1; +} + + +void replay_save_time_t(time_t tm) +{ + replay_save_instructions(); + + if (replay_file) + { + replay_put_event(EVENT_TIME_T); + if (sizeof(tm) == 4) + { + replay_put_dword(tm); + } + else if (sizeof(tm) == 8) + { + replay_put_qword(tm); + } + else + { + fprintf(stderr, "invalid time_t sizeof: %u\n", (unsigned)sizeof(tm)); + exit(1); + } + } +} + +time_t replay_read_time_t(void) +{ + if (play_submode == REPLAY_PLAY_CHANGED) + { + fprintf(stderr, "Cannot read log in replay run mode\n"); + exit(1); + } + + if (replay_file) + { + time_t tm; + + if (skip_async_events_until(EVENT_TIME_T)) + { + // TODO: return cached value + fprintf(stderr, "Cannot read time_t when stopped\n"); + exit(1); + } + + if (sizeof(tm) == 4) + { + tm = replay_get_dword(); + } + else if (sizeof(tm) == 8) + { + tm = replay_get_qword(); + } + else + { + fprintf(stderr, "invalid time_t sizeof: %u\n", (unsigned)sizeof(tm)); + exit(1); + } + + replay_check_error(); + + replay_has_unread_data = 0; + + return tm; + } + + fprintf(stderr, "REPLAY INTERNAL ERROR %d\n", __LINE__); + exit(1); +} + +void replay_save_tm(struct tm *tm) +{ + replay_save_instructions(); + + if (replay_file) + { + replay_put_event(EVENT_TM); + + replay_put_dword(tm->tm_sec); + replay_put_dword(tm->tm_min); + replay_put_dword(tm->tm_hour); + replay_put_dword(tm->tm_mday); + replay_put_dword(tm->tm_mon); + replay_put_dword(tm->tm_year); + replay_put_dword(tm->tm_wday); + replay_put_dword(tm->tm_yday); + replay_put_dword(tm->tm_isdst); + } +} + +void replay_read_tm(struct tm *tm) +{ + if (play_submode == REPLAY_PLAY_CHANGED) + { + fprintf(stderr, "Cannot read log in replay run mode\n"); + exit(1); + } + + if (replay_file) + { + if (skip_async_events_until(EVENT_TM)) + { + return; + } + + tm->tm_sec = replay_get_dword(); + tm->tm_min = replay_get_dword(); + tm->tm_hour = replay_get_dword(); + tm->tm_mday = replay_get_dword(); + tm->tm_mon = replay_get_dword(); + tm->tm_year = replay_get_dword(); + tm->tm_wday = replay_get_dword(); + tm->tm_yday = replay_get_dword(); + tm->tm_isdst = replay_get_dword(); + + replay_check_error(); + replay_has_unread_data = 0; + + return; + } + + fprintf(stderr, "REPLAY INTERNAL ERROR %d\n", __LINE__); + exit(1); +} + + + +void replay_undo_last_instruction(void) +{ + if (replay_mode == REPLAY_SAVE + || play_submode == REPLAY_PLAY_CHANGED) + { + first_cpu->instructions_count--; + } + else + { + replay_state.skipping_instruction = 1; + } +} + + +uint64_t replay_get_current_step(void) +{ + if (first_cpu == NULL) + return 0; + if (replay_file) + { + if (replay_mode == REPLAY_SAVE) + { + return replay_state.current_step + first_cpu->instructions_count; + } + } + return replay_state.current_step; +} + + +void replay_set_break(uint64_t step) +{ + replay_break_step = step; +} + + +SavedStateInfo *find_nearest_state(uint64_t step) +{ + SavedStateInfo *first = saved_states; + SavedStateInfo *last = saved_states + saved_states_count; + while (first < last - 1) + { + SavedStateInfo *next = first + (last - first) / 2; + if (next->step > step) + last = next; + else + first = next; + } + + return first; +} + + +int replay_seek_step(uint64_t step) +{ + if (replay_mode != REPLAY_PLAY) + return 0; + + // load VM state, if possible + if (saved_states_count > 0) + { + // find VM state to load + SavedStateInfo *first = find_nearest_state(step); + + if (first->step <= step + && (replay_get_current_step() > step + || replay_get_current_step() < first->step + // always load the state, if something was changed + || play_submode == REPLAY_PLAY_CHANGED)) + { + char name[128]; + bool running = runstate_is_running(); + if (running && !qemu_in_vcpu_thread()) + { + vm_stop(RUN_STATE_RESTORE_VM); + } + else + { + cpu_disable_ticks(); + } + + replay_clear_events(); + replay_sound_flush_queue(); + + // reset the mode before loading to allow correct timers operation + if (/*play_submode == REPLAY_PLAY_STOPPED + || */play_submode == REPLAY_PLAY_CHANGED) + { + play_submode = REPLAY_PLAY_NORMAL; + } + + current_saved_state = first - saved_states; + sprintf(name, SAVED_STATE_NAME_FORMAT, current_saved_state); + if (load_vmstate(name) < 0) + { + fprintf(stderr, "Replay: cannot load VM state while seeking\n"); + exit(1); + } + // check end event + check_savevm(); + + tb_flush_all(); + + cpu_enable_ticks(); + if (running && !qemu_in_vcpu_thread()) + { + vm_start(); + } + + replay_fetch_data_kind(); + while (replay_data_kind >= EVENT_CLOCK && replay_data_kind < EVENT_CLOCK + REPLAY_CLOCK_COUNT) + { + replay_read_next_clock(-1); + replay_fetch_data_kind(); + } + } + } + + // setup the breakpoint + if (step >= replay_get_current_step()) + { + replay_set_break(step); + return 1; + } + + return 0; +} + + + +void replay_goto_step(uint64_t step) +{ + if (replay_seek_step(step)) + { + play_submode = REPLAY_PLAY_GOTO; + } +} + + +void replay_events(struct Monitor *mon) +{ + if (replay_mode == REPLAY_PLAY && replay_file) + { + int ok = 1; + // becomes 1 when grey key is pressed or released + uint64_t offset = ftello64(replay_file); + uint64_t step = 0; + fseek(replay_file, HEADER_SIZE, SEEK_SET); + + // read whole file and print the events + while (ok && !feof(replay_file)) + { + uint8_t data; + size_t size; + fread(&data, sizeof(data), 1, replay_file); + switch (data) { + case EVENT_END_STARTUP: + break; + case EVENT_TIME_T: + fseek(replay_file, sizeof(time_t), SEEK_CUR); + break; + case EVENT_TM: + fseek(replay_file, sizeof(uint32_t) * 9, SEEK_CUR); + break; + case EVENT_SOUND_OUT: + fseek(replay_file, sizeof(uint32_t), SEEK_CUR); + break; + case EVENT_SOUND_IN: + fread(&size, sizeof(size), 1, replay_file); + fseek(replay_file, size, SEEK_CUR); + break; + case EVENT_SAVE_VM_BEGIN: + monitor_printf(mon, "%" PRId64 ": VM state\n", step); + break; + case EVENT_SAVE_VM_END: + break; + case EVENT_INTERRUPT: + case EVENT_EXCEPTION: + break; + case EVENT_SHUTDOWN: + monitor_printf(mon, "%" PRId64 ": Request for closing simulator window\n", step); + break; + case EVENT_ASSERT: + fseek(replay_file, sizeof(uint64_t), SEEK_CUR); + break; + case EVENT_TARGET_TIME: + { + int32_t sec, min, hour, day, month, year, + hsec, hmin, hhour, hday, hmonth, hyear; + sec = replay_get_dword(); + min = replay_get_dword(); + hour = replay_get_dword(); + day = replay_get_dword(); + month = replay_get_dword(); + year = replay_get_dword(); + hsec = replay_get_dword(); + hmin = replay_get_dword(); + hhour = replay_get_dword(); + hday = replay_get_dword(); + hmonth = replay_get_dword(); + hyear = replay_get_dword(); + monitor_printf(mon, "%" PRId64 ": Target time %d.%02d.%02d-%02d:%02d:%02d Host time %d.%02d.%02d-%02d:%02d:%02d\n", + step, year, month, day, hour, min, sec, hyear, hmonth, hday, hhour, hmin, hsec); + } + break; + case EVENT_INSTRUCTION: + { + uint32_t count; + fread(&count, sizeof(count), 1, replay_file); + step += count; + } + break; + case EVENT_DATA_INT: + fseek(replay_file, sizeof(uint32_t), SEEK_CUR); + break; + case EVENT_DATA_BUFFER: + fread(&size, sizeof(size), 1, replay_file); + fseek(replay_file, size, SEEK_CUR); + break; + case EVENT_END: + ok = 0; + break; + case EVENT_ASYNC: + case EVENT_ASYNC_OPT: + replay_skip_async_event(mon, step, data == EVENT_ASYNC_OPT); + break; + default: + if (data >= EVENT_CLOCK && data < EVENT_CHECKPOINT) + { + fseek(replay_file, sizeof(uint64_t), SEEK_CUR); + } + else if (data >= EVENT_CHECKPOINT && data < EVENT_END) + { + // skip + } + else + { + ok = 0; + monitor_printf(mon, "%" PRId64 ": Found unknown event %d\n", step, data); + } + break; + } + } + fseeko64(replay_file, offset, SEEK_SET); + } +} + + +int replay_xtoi(char c) +{ + switch (c) + { + case 'a': + case 'A': + return 10; + case 'b': + case 'B': + return 11; + case 'c': + case 'C': + return 12; + case 'd': + case 'D': + return 13; + case 'e': + case 'E': + return 14; + case 'f': + case 'F': + return 15; + default: + if (isdigit(c)) + return c - '0'; + + return 0; + } + + return 0; +} + +int64_t replay_get_file_offset(void) +{ + return (int64_t) ftello64(replay_file); +} + +void replay_register_get_time(replay_get_date_func f, void *opaque) +{ + if (replay_mode != REPLAY_NONE) + { + replay_get_target_time = f; + replay_target_time_dev = opaque; + } +} + +void replay_data_int(int *data) +{ + if (replay_file && replay_mode == REPLAY_PLAY) + { + if (play_submode == REPLAY_PLAY_CHANGED) + { + fprintf(stderr, "Cannot read log in replay run mode\n"); + exit(1); + } + skip_async_events(EVENT_DATA_INT); + validate_data_kind(EVENT_DATA_INT); + *data = replay_get_dword(); + replay_check_error(); + replay_has_unread_data = 0; + } + else if (replay_file && replay_mode == REPLAY_SAVE) + { + replay_save_instructions(); + replay_put_event(EVENT_DATA_INT); + replay_put_dword(*data); + } +} + +void replay_data_buffer(unsigned char *data, size_t size) +{ + if (replay_file && replay_mode == REPLAY_PLAY) + { + if (play_submode == REPLAY_PLAY_CHANGED) + { + fprintf(stderr, "Cannot read log in replay run mode\n"); + exit(1); + } + size_t read_size = 0; + skip_async_events(EVENT_DATA_BUFFER); + validate_data_kind(EVENT_DATA_BUFFER); + replay_get_array(data, &read_size); + replay_check_error(); + if (read_size != size) + { + fprintf(stderr, "Replay: read non-matching size of the buffer\n"); + exit(1); + } + replay_has_unread_data = 0; + } + else if (replay_file && replay_mode == REPLAY_SAVE) + { + replay_save_instructions(); + replay_put_event(EVENT_DATA_BUFFER); + replay_put_array(data, size); + } +} + diff --git a/replay/replay.h b/replay/replay.h new file mode 100644 index 0000000..fc72a92 --- /dev/null +++ b/replay/replay.h @@ -0,0 +1,249 @@ +#ifndef REPLAY_H +#define REPLAY_H + +#include +#ifdef _WIN32 +#include +#include +#endif +#include "qemu/option_int.h" +#include "qemu/typedefs.h" + +struct QemuOpts; +struct VncDisplay; +struct Monitor; +struct ISADevice; +typedef struct CharDriverState CharDriverState; +struct libusb_transfer; + +/* replay modes */ +#define REPLAY_NONE 0 +#define REPLAY_SAVE 1 +#define REPLAY_PLAY 2 +/* replay submodes */ +#define REPLAY_PLAY_UNKNOWN 0 +/* Normal log replaying */ +#define REPLAY_PLAY_NORMAL 1 +/* Replaying for reverse execution */ +#define REPLAY_PLAY_REVERSE 2 +/* Running from the some starting point in the replay mode + or after changing registers, memory, or network packet. */ +#define REPLAY_PLAY_CHANGED 3 +/* Replaying for seeking to the specified step of the trace */ +#define REPLAY_PLAY_GOTO 4 + +/* replay clock kinds */ +/* rdtsc */ +#define REPLAY_CLOCK_REAL_TICKS 0 +/* host_clock */ +#define REPLAY_CLOCK_REALTIME 1 +/* vm_clock */ +#define REPLAY_CLOCK_VIRTUAL 2 + +#define REPLAY_CLOCK_COUNT 3 + +/* replay async event kinds */ +#define REPLAY_ASYNC_EVENT_NOTIFIER 1 +#define REPLAY_ASYNC_EVENT_NETWORK 2 +#define REPLAY_ASYNC_EVENT_THREAD 3 +#define REPLAY_ASYNC_EVENT_BH 4 +#define REPLAY_ASYNC_EVENT_USB_CTRL 5 +#define REPLAY_ASYNC_EVENT_USB_DATA 6 +#define REPLAY_ASYNC_EVENT_USB_ISO 7 +#define REPLAY_ASYNC_EVENT_CHAR 8 +#define REPLAY_ASYNC_EVENT_INPUT 9 +#define REPLAY_ASYNC_EVENT_INPUT_SYNC 10 + +#define REPLAY_ASYNC_COUNT 11 + +extern int replay_mode; +extern char *replay_image_suffix; +/*! Shift value for icount based on replay or zero, if it is disabled. */ +extern int replay_icount; +extern int not_compatible_replay_param; +extern uint64_t replay_network_packets; + +/*! Returns replay play submode */ +int replay_get_play_submode(void); +/*! Enter "something changed" replay submode */ +void replay_set_play_changed(void); +void replay_init_overlay(void); +/*! Enables recording or saving event log with specified parameters */ +void replay_configure(struct QemuOpts *opts, int mode); +//void replay_enable(const char *fname, int mode); +void replay_init_timer(void); +void replay_add_network_client(struct NetClientState *nc); +/*! Retrieves name of the file, which is used for recording + or replaying execution log. */ +const char *replay_get_filename(void); +int64_t replay_get_file_offset(void); + +//! Puts debug information into the log in save mode +//! and checks this information in play mode. +void replay_assert(uint64_t value, const char *message); + +void replay_save_clock(unsigned int kind, int64_t clock); +int64_t replay_read_clock(unsigned int kind); + +//! Called to write software interrupt event into the log. +//! This event enables breaking the translation block execution. +void replay_write_interrupt_request(void); +//! Tries to read interrupt event from the file +//! and sets interrupt pending flag if the event was read. +//! Returns non-zero, when interrupt request is pending. +int replay_read_interrupt_request(void); +//! Resets interrupt request flag. +void replay_reset_interrupt_request(void); +//! Called by interrupt handlers to write or read +//! interrupt processing events. +//! \return non-zero if interrupt should be processed +int replay_interrupt_request(void); + +//! Called by exception handler to write or read +//! exception processing events. +bool replay_exception(void); + +int replay_has_timer_request(void); + +//! Called when qemu shutdown is requested. +void replay_shutdown_request(void); + +//! Closes replay file. +void replay_finish(void); + +//! Returns non-zero if next event is instruction. +int replay_has_instruction(void); +//! Returns true if next event can be processed by cpu-exec. +bool replay_has_code(void); +//! Should be called at check points in the execution. +//! Saves instructions count in the SAVE mode and validates (skipping +//! the interrupts) in the PLAY mode. +//! \param process_timer Contains non-zero value, when timer requests should be processed. +//! This parameter is not taken into account in PLAY mode. +void replay_instruction(int process_timer); +bool replay_has_checkpoint(unsigned int checkpoint); +int replay_checkpoint(unsigned int checkpoint); + +void replay_save_time_t(time_t tm); +time_t replay_read_time_t(void); + +void replay_save_tm(struct tm *tm); +void replay_read_tm(struct tm *tm); + +void replay_save_net_packet(NetClientState *nc, const uint8_t *buf, size_t size); + +/* Sound functions may be called from another thread */ +#ifdef _WIN32 +void replay_sound_in_event(WAVEHDR *hdr); +/*! Adds header to the queue. + In SAVE mode this header is queued for saving into log. + In PLAY mode this header is queued for reading from log. + Returns 1 in PLAY mode when queue is full. + Otherwise returns 0. +*/ +int replay_sound_out_event(WAVEHDR *hdr); +//! Initializes queue for sound input. +void replay_init_sound_in(void *instance, WAVEHDR *hdrs, int sz); +//! Initializes queue for sound output. +void replay_init_sound_out(void *instance, WAVEHDR *hdrs, int sz); +#endif + +void replay_undo_last_instruction(void); + +/* Functions to retrieve information about replay process. + Used by monitor module. */ + +/*! Returns number of passed instructions and timer events. */ +uint64_t replay_get_current_step(void); +/*! Sets step where execution should be stopped. */ +void replay_set_break(uint64_t step); +/*! Seeks to the specified step. + Loads VM state, if possible, and sets break to specified step. + Returns nonzero, when seeking was successful. + */ +int replay_seek_step(uint64_t step); +/*! Calls replay_seek_step and switches to REPLAY_PLAY_GOTO mode. */ +void replay_goto_step(uint64_t step); + +/*! Prints events from the log. */ +void replay_events(struct Monitor *mon); +/*! Prints desired network packet contents. + Returns non-zero, if packet was successfully printed. + */ +int replay_get_packet(struct Monitor *mon, int64_t id); +/*! Adds desired packet to the list of the "modified" packets. + When replaying, packet with the specified id read from the file + will be replaced with packet, added with this command. + If bytes is null, modified packet with desired id will be + removed from the list. + Returns non-zero, if packet was successfully added or removed. + */ +int replay_set_packet(struct Monitor *mon, int64_t id, const char *bytes); +/* utility function */ +int replay_xtoi(char); + +/*! Initializes reverse continue execution. + Called by debugger module. */ +void replay_reverse_continue(void); +/*! Initializes reverse step execution. + Called by debugger module. */ +void replay_reverse_step(void); +/* Called instead of invoking breakpoint in reverse continue mode. */ +void replay_reverse_breakpoint(void); + +/* Save and load USB events */ +/*! Called in save mode when xfer for ctrl usb data is completed */ +void replay_req_complete_ctrl(struct libusb_transfer *xfer); +/*! Called in play mode to register xfer to be completed by reading it from the log */ +void replay_req_register_ctrl(struct libusb_transfer *xfer); +/*! Called in save mode when xfer for usb data request is completed */ +void replay_req_complete_data(struct libusb_transfer *xfer); +/*! Called in play mode to register xfer to be completed by reading it from the log */ +void replay_req_register_data(struct libusb_transfer *xfer); +/*! Called in save mode when iso xfer for usb data request is completed */ +void replay_req_complete_iso(struct libusb_transfer *xfer); +/*! Called in play mode to register iso xfer to be completed by reading it from the log */ +void replay_req_register_iso(struct libusb_transfer *xfer); + +#define REPLAY_ASSERT(val) replay_assert(val, NULL) +#define REPLAY_ASSERT_M(val, msg) replay_assert(val, msg) + +typedef void (*replay_get_date_func)(struct ISADevice*, struct tm *); + +/*! Registers function which gets time from target OS. */ +void replay_register_get_time(replay_get_date_func f, void *opaque); + +/* Async events functions */ +/*! Adds specified async event to the queue */ +void replay_add_event(unsigned int event_id, void *opaque); +/*! Adds specified async event with 2 parameters to the queue */ +void replay_add_event2(unsigned int event_id, void *opaque, void *opaque2); +/*! Adds BH event to the queue */ +void replay_add_bh_event(void *bh, uint64_t id); +/*! Adds thread event to the queue */ +void replay_add_thread_event(void *opaque, void *opaque2, uint64_t id); + +void replay_enable_events(void); +void replay_disable_events(void); + +/*! Registers char driver to save it's events */ +void replay_register_char_driver(CharDriverState *chr); + +/*! Writes or reads integer value to/from replay log. */ +void replay_data_int(int *data); +/*! Macro for using replay_data_int function */ +#define REPLAY_DATA_INT(var, value) ((replay_mode == REPLAY_NONE ? var = (value) : replay_mode == REPLAY_SAVE ? (var = (value), replay_data_int(&var), var) : /* PLAY */ (replay_data_int(&var), var))) +/*! Writes or reads buffer to/from replay log. */ +void replay_data_buffer(unsigned char *data, size_t size); +/*! Macro for using replay_data_buffer function */ +#define REPLAY_DATA_VAR(var) replay_data_buffer((unsigned char*)&(var), sizeof(var)) +/*! Saves write to char device event to the log */ +void replay_chr_be_write(CharDriverState *s, uint8_t *buf, int len); + +/* Returns the virtual CPU time, based on the instruction counter. */ +int64_t replay_get_icount(void); +void replay_clock_warp(void); + +/*! Starts normal execution from the current replay step. */