new file mode 100644
@@ -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
new file mode 100644
@@ -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
+}
+
new file mode 100644
@@ -0,0 +1,119 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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);
+}
new file mode 100644
@@ -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);
+ }
+}
new file mode 100644
@@ -0,0 +1,463 @@
+#include <stdio.h>
+#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;
+ }
+}
+
new file mode 100644
@@ -0,0 +1,112 @@
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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);
+}
new file mode 100644
@@ -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;
+ }
+}
+
new file mode 100644
@@ -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);
+ }
+}
+
new file mode 100644
@@ -0,0 +1,192 @@
+#ifndef REPLAY_INTERNAL_H
+#define REPLAY_INTERNAL_H
+
+#include <stdio.h>
+
+// 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
new file mode 100644
@@ -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);
+}
+
new file mode 100644
@@ -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
+}
new file mode 100644
@@ -0,0 +1,1604 @@
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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);
+ }
+}
+
new file mode 100644
@@ -0,0 +1,249 @@
+#ifndef REPLAY_H
+#define REPLAY_H
+
+#include <time.h>
+#ifdef _WIN32
+#include <windows.h>
+#include <mmsystem.h>
+#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. */
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 <pavel.dovgaluk@gmail.com> --- +void replay_change(struct Monitor *mon); +#endif