xref: /qemu/replay/replay.c (revision 677a3baba4356b91c7acfb5828dd9a598decda6a)
1d73abd6dSPavel Dovgalyuk /*
2d73abd6dSPavel Dovgalyuk  * replay.c
3d73abd6dSPavel Dovgalyuk  *
4d73abd6dSPavel Dovgalyuk  * Copyright (c) 2010-2015 Institute for System Programming
5d73abd6dSPavel Dovgalyuk  *                         of the Russian Academy of Sciences.
6d73abd6dSPavel Dovgalyuk  *
7d73abd6dSPavel Dovgalyuk  * This work is licensed under the terms of the GNU GPL, version 2 or later.
8d73abd6dSPavel Dovgalyuk  * See the COPYING file in the top-level directory.
9d73abd6dSPavel Dovgalyuk  *
10d73abd6dSPavel Dovgalyuk  */
11d73abd6dSPavel Dovgalyuk 
12d38ea87aSPeter Maydell #include "qemu/osdep.h"
13da34e65cSMarkus Armbruster #include "qapi/error.h"
14d73abd6dSPavel Dovgalyuk #include "sysemu/replay.h"
1554d31236SMarkus Armbruster #include "sysemu/runstate.h"
1626bc60acSPavel Dovgalyuk #include "replay-internal.h"
1726bc60acSPavel Dovgalyuk #include "qemu/timer.h"
188b427044SPavel Dovgalyuk #include "qemu/main-loop.h"
19922a01a0SMarkus Armbruster #include "qemu/option.h"
20d2528bdcSPaolo Bonzini #include "sysemu/cpus.h"
217615936eSPavel Dovgalyuk #include "qemu/error-report.h"
227615936eSPavel Dovgalyuk 
237615936eSPavel Dovgalyuk /* Current version of the replay mechanism.
247615936eSPavel Dovgalyuk    Increase it when file format changes. */
25*677a3babSPavel Dovgalyuk #define REPLAY_VERSION              0xe0200a
267615936eSPavel Dovgalyuk /* Size of replay log header */
277615936eSPavel Dovgalyuk #define HEADER_SIZE                 (sizeof(uint32_t) + sizeof(uint64_t))
28d73abd6dSPavel Dovgalyuk 
29d73abd6dSPavel Dovgalyuk ReplayMode replay_mode = REPLAY_MODE_NONE;
309c2037d0SPavel Dovgalyuk char *replay_snapshot;
3126bc60acSPavel Dovgalyuk 
327615936eSPavel Dovgalyuk /* Name of replay file  */
337615936eSPavel Dovgalyuk static char *replay_filename;
3426bc60acSPavel Dovgalyuk ReplayState replay_state;
350194749aSPavel Dovgalyuk static GSList *replay_blockers;
3626bc60acSPavel Dovgalyuk 
3726bc60acSPavel Dovgalyuk bool replay_next_event_is(int event)
3826bc60acSPavel Dovgalyuk {
3926bc60acSPavel Dovgalyuk     bool res = false;
4026bc60acSPavel Dovgalyuk 
4126bc60acSPavel Dovgalyuk     /* nothing to skip - not all instructions used */
4213f26713SPavel Dovgalyuk     if (replay_state.instruction_count != 0) {
43f186d64dSPavel Dovgalyuk         assert(replay_state.data_kind == EVENT_INSTRUCTION);
4426bc60acSPavel Dovgalyuk         return event == EVENT_INSTRUCTION;
4526bc60acSPavel Dovgalyuk     }
4626bc60acSPavel Dovgalyuk 
4726bc60acSPavel Dovgalyuk     while (true) {
48e957ad8aSPavel Dovgalyuk         unsigned int data_kind = replay_state.data_kind;
49e957ad8aSPavel Dovgalyuk         if (event == data_kind) {
5026bc60acSPavel Dovgalyuk             res = true;
5126bc60acSPavel Dovgalyuk         }
52e957ad8aSPavel Dovgalyuk         switch (data_kind) {
53802f045aSEric Blake         case EVENT_SHUTDOWN ... EVENT_SHUTDOWN_LAST:
54b60c48a7SPavel Dovgalyuk             replay_finish_event();
55e957ad8aSPavel Dovgalyuk             qemu_system_shutdown_request(data_kind - EVENT_SHUTDOWN);
56b60c48a7SPavel Dovgalyuk             break;
5726bc60acSPavel Dovgalyuk         default:
5826bc60acSPavel Dovgalyuk             /* clock, time_t, checkpoint and other events */
5926bc60acSPavel Dovgalyuk             return res;
6026bc60acSPavel Dovgalyuk         }
6126bc60acSPavel Dovgalyuk     }
6226bc60acSPavel Dovgalyuk     return res;
6326bc60acSPavel Dovgalyuk }
6426bc60acSPavel Dovgalyuk 
6513f26713SPavel Dovgalyuk uint64_t replay_get_current_icount(void)
6626bc60acSPavel Dovgalyuk {
6726bc60acSPavel Dovgalyuk     return cpu_get_icount_raw();
6826bc60acSPavel Dovgalyuk }
698b427044SPavel Dovgalyuk 
708b427044SPavel Dovgalyuk int replay_get_instructions(void)
718b427044SPavel Dovgalyuk {
728b427044SPavel Dovgalyuk     int res = 0;
738b427044SPavel Dovgalyuk     replay_mutex_lock();
748b427044SPavel Dovgalyuk     if (replay_next_event_is(EVENT_INSTRUCTION)) {
7513f26713SPavel Dovgalyuk         res = replay_state.instruction_count;
768b427044SPavel Dovgalyuk     }
778b427044SPavel Dovgalyuk     replay_mutex_unlock();
788b427044SPavel Dovgalyuk     return res;
798b427044SPavel Dovgalyuk }
808b427044SPavel Dovgalyuk 
818b427044SPavel Dovgalyuk void replay_account_executed_instructions(void)
828b427044SPavel Dovgalyuk {
838b427044SPavel Dovgalyuk     if (replay_mode == REPLAY_MODE_PLAY) {
84d759c951SAlex Bennée         g_assert(replay_mutex_locked());
8513f26713SPavel Dovgalyuk         if (replay_state.instruction_count > 0) {
8613f26713SPavel Dovgalyuk             int count = (int)(replay_get_current_icount()
8713f26713SPavel Dovgalyuk                               - replay_state.current_icount);
88982263ceSAlex Bennée 
89982263ceSAlex Bennée             /* Time can only go forward */
90982263ceSAlex Bennée             assert(count >= 0);
91982263ceSAlex Bennée 
9213f26713SPavel Dovgalyuk             replay_state.instruction_count -= count;
9313f26713SPavel Dovgalyuk             replay_state.current_icount += count;
9413f26713SPavel Dovgalyuk             if (replay_state.instruction_count == 0) {
95f186d64dSPavel Dovgalyuk                 assert(replay_state.data_kind == EVENT_INSTRUCTION);
968b427044SPavel Dovgalyuk                 replay_finish_event();
978b427044SPavel Dovgalyuk                 /* Wake up iothread. This is required because
988b427044SPavel Dovgalyuk                    timers will not expire until clock counters
998b427044SPavel Dovgalyuk                    will be read from the log. */
1008b427044SPavel Dovgalyuk                 qemu_notify_event();
1018b427044SPavel Dovgalyuk             }
1028b427044SPavel Dovgalyuk         }
1038b427044SPavel Dovgalyuk     }
1048b427044SPavel Dovgalyuk }
1056f060969SPavel Dovgalyuk 
1066f060969SPavel Dovgalyuk bool replay_exception(void)
1076f060969SPavel Dovgalyuk {
108d759c951SAlex Bennée 
1096f060969SPavel Dovgalyuk     if (replay_mode == REPLAY_MODE_RECORD) {
110d759c951SAlex Bennée         g_assert(replay_mutex_locked());
1116f060969SPavel Dovgalyuk         replay_save_instructions();
1126f060969SPavel Dovgalyuk         replay_put_event(EVENT_EXCEPTION);
1136f060969SPavel Dovgalyuk         return true;
1146f060969SPavel Dovgalyuk     } else if (replay_mode == REPLAY_MODE_PLAY) {
115d759c951SAlex Bennée         g_assert(replay_mutex_locked());
1166f060969SPavel Dovgalyuk         bool res = replay_has_exception();
1176f060969SPavel Dovgalyuk         if (res) {
1186f060969SPavel Dovgalyuk             replay_finish_event();
1196f060969SPavel Dovgalyuk         }
1206f060969SPavel Dovgalyuk         return res;
1216f060969SPavel Dovgalyuk     }
1226f060969SPavel Dovgalyuk 
1236f060969SPavel Dovgalyuk     return true;
1246f060969SPavel Dovgalyuk }
1256f060969SPavel Dovgalyuk 
1266f060969SPavel Dovgalyuk bool replay_has_exception(void)
1276f060969SPavel Dovgalyuk {
1286f060969SPavel Dovgalyuk     bool res = false;
1296f060969SPavel Dovgalyuk     if (replay_mode == REPLAY_MODE_PLAY) {
130d759c951SAlex Bennée         g_assert(replay_mutex_locked());
1316f060969SPavel Dovgalyuk         replay_account_executed_instructions();
1326f060969SPavel Dovgalyuk         res = replay_next_event_is(EVENT_EXCEPTION);
1336f060969SPavel Dovgalyuk     }
1346f060969SPavel Dovgalyuk 
1356f060969SPavel Dovgalyuk     return res;
1366f060969SPavel Dovgalyuk }
1376f060969SPavel Dovgalyuk 
1386f060969SPavel Dovgalyuk bool replay_interrupt(void)
1396f060969SPavel Dovgalyuk {
1406f060969SPavel Dovgalyuk     if (replay_mode == REPLAY_MODE_RECORD) {
141d759c951SAlex Bennée         g_assert(replay_mutex_locked());
1426f060969SPavel Dovgalyuk         replay_save_instructions();
1436f060969SPavel Dovgalyuk         replay_put_event(EVENT_INTERRUPT);
1446f060969SPavel Dovgalyuk         return true;
1456f060969SPavel Dovgalyuk     } else if (replay_mode == REPLAY_MODE_PLAY) {
146d759c951SAlex Bennée         g_assert(replay_mutex_locked());
1476f060969SPavel Dovgalyuk         bool res = replay_has_interrupt();
1486f060969SPavel Dovgalyuk         if (res) {
1496f060969SPavel Dovgalyuk             replay_finish_event();
1506f060969SPavel Dovgalyuk         }
1516f060969SPavel Dovgalyuk         return res;
1526f060969SPavel Dovgalyuk     }
1536f060969SPavel Dovgalyuk 
1546f060969SPavel Dovgalyuk     return true;
1556f060969SPavel Dovgalyuk }
1566f060969SPavel Dovgalyuk 
1576f060969SPavel Dovgalyuk bool replay_has_interrupt(void)
1586f060969SPavel Dovgalyuk {
1596f060969SPavel Dovgalyuk     bool res = false;
1606f060969SPavel Dovgalyuk     if (replay_mode == REPLAY_MODE_PLAY) {
161d759c951SAlex Bennée         g_assert(replay_mutex_locked());
1626f060969SPavel Dovgalyuk         replay_account_executed_instructions();
1636f060969SPavel Dovgalyuk         res = replay_next_event_is(EVENT_INTERRUPT);
1646f060969SPavel Dovgalyuk     }
1656f060969SPavel Dovgalyuk     return res;
1666f060969SPavel Dovgalyuk }
167b60c48a7SPavel Dovgalyuk 
168802f045aSEric Blake void replay_shutdown_request(ShutdownCause cause)
169b60c48a7SPavel Dovgalyuk {
170b60c48a7SPavel Dovgalyuk     if (replay_mode == REPLAY_MODE_RECORD) {
171d759c951SAlex Bennée         g_assert(replay_mutex_locked());
172802f045aSEric Blake         replay_put_event(EVENT_SHUTDOWN + cause);
173b60c48a7SPavel Dovgalyuk     }
174b60c48a7SPavel Dovgalyuk }
1758bd7f71dSPavel Dovgalyuk 
1768bd7f71dSPavel Dovgalyuk bool replay_checkpoint(ReplayCheckpoint checkpoint)
1778bd7f71dSPavel Dovgalyuk {
1788bd7f71dSPavel Dovgalyuk     bool res = false;
17966eb7825SPavel Dovgalyuk     static bool in_checkpoint;
1808bd7f71dSPavel Dovgalyuk     assert(EVENT_CHECKPOINT + checkpoint <= EVENT_CHECKPOINT_LAST);
1818bd7f71dSPavel Dovgalyuk 
1828bd7f71dSPavel Dovgalyuk     if (!replay_file) {
1838bd7f71dSPavel Dovgalyuk         return true;
1848bd7f71dSPavel Dovgalyuk     }
1858bd7f71dSPavel Dovgalyuk 
18666eb7825SPavel Dovgalyuk     if (in_checkpoint) {
18766eb7825SPavel Dovgalyuk         /* If we are already in checkpoint, then there is no need
18866eb7825SPavel Dovgalyuk            for additional synchronization.
18966eb7825SPavel Dovgalyuk            Recursion occurs when HW event modifies timers.
19066eb7825SPavel Dovgalyuk            Timer modification may invoke the checkpoint and
19166eb7825SPavel Dovgalyuk            proceed to recursion. */
19266eb7825SPavel Dovgalyuk         return true;
19366eb7825SPavel Dovgalyuk     }
19466eb7825SPavel Dovgalyuk     in_checkpoint = true;
19566eb7825SPavel Dovgalyuk 
19666eb7825SPavel Dovgalyuk     replay_save_instructions();
1978bd7f71dSPavel Dovgalyuk 
1988bd7f71dSPavel Dovgalyuk     if (replay_mode == REPLAY_MODE_PLAY) {
199d759c951SAlex Bennée         g_assert(replay_mutex_locked());
2008bd7f71dSPavel Dovgalyuk         if (replay_next_event_is(EVENT_CHECKPOINT + checkpoint)) {
2018bd7f71dSPavel Dovgalyuk             replay_finish_event();
202f186d64dSPavel Dovgalyuk         } else if (replay_state.data_kind != EVENT_ASYNC) {
2038bd7f71dSPavel Dovgalyuk             res = false;
2048bd7f71dSPavel Dovgalyuk             goto out;
2058bd7f71dSPavel Dovgalyuk         }
2068bd7f71dSPavel Dovgalyuk         replay_read_events(checkpoint);
2078bd7f71dSPavel Dovgalyuk         /* replay_read_events may leave some unread events.
2088bd7f71dSPavel Dovgalyuk            Return false if not all of the events associated with
2098bd7f71dSPavel Dovgalyuk            checkpoint were processed */
210f186d64dSPavel Dovgalyuk         res = replay_state.data_kind != EVENT_ASYNC;
2118bd7f71dSPavel Dovgalyuk     } else if (replay_mode == REPLAY_MODE_RECORD) {
212d759c951SAlex Bennée         g_assert(replay_mutex_locked());
2138bd7f71dSPavel Dovgalyuk         replay_put_event(EVENT_CHECKPOINT + checkpoint);
21489e46eb4SPavel Dovgalyuk         /* This checkpoint belongs to several threads.
21589e46eb4SPavel Dovgalyuk            Processing events from different threads is
21689e46eb4SPavel Dovgalyuk            non-deterministic */
217ca9759c2SPavel Dovgalyuk         if (checkpoint != CHECKPOINT_CLOCK_WARP_START
218ca9759c2SPavel Dovgalyuk             /* FIXME: this is temporary fix, other checkpoints
219ca9759c2SPavel Dovgalyuk                       may also be invoked from the different threads someday.
220ca9759c2SPavel Dovgalyuk                       Asynchronous event processing should be refactored
221ca9759c2SPavel Dovgalyuk                       to create additional replay event kind which is
222ca9759c2SPavel Dovgalyuk                       nailed to the one of the threads and which processes
223ca9759c2SPavel Dovgalyuk                       the event queue. */
224ca9759c2SPavel Dovgalyuk             && checkpoint != CHECKPOINT_CLOCK_VIRTUAL) {
2258bd7f71dSPavel Dovgalyuk             replay_save_events(checkpoint);
22689e46eb4SPavel Dovgalyuk         }
2278bd7f71dSPavel Dovgalyuk         res = true;
2288bd7f71dSPavel Dovgalyuk     }
2298bd7f71dSPavel Dovgalyuk out:
23066eb7825SPavel Dovgalyuk     in_checkpoint = false;
2318bd7f71dSPavel Dovgalyuk     return res;
2328bd7f71dSPavel Dovgalyuk }
2337615936eSPavel Dovgalyuk 
2340c08185fSPavel Dovgalyuk bool replay_has_checkpoint(void)
2350c08185fSPavel Dovgalyuk {
2360c08185fSPavel Dovgalyuk     bool res = false;
2370c08185fSPavel Dovgalyuk     if (replay_mode == REPLAY_MODE_PLAY) {
2380c08185fSPavel Dovgalyuk         g_assert(replay_mutex_locked());
2390c08185fSPavel Dovgalyuk         replay_account_executed_instructions();
2400c08185fSPavel Dovgalyuk         res = EVENT_CHECKPOINT <= replay_state.data_kind
2410c08185fSPavel Dovgalyuk               && replay_state.data_kind <= EVENT_CHECKPOINT_LAST;
2420c08185fSPavel Dovgalyuk     }
2430c08185fSPavel Dovgalyuk     return res;
2440c08185fSPavel Dovgalyuk }
2450c08185fSPavel Dovgalyuk 
2467615936eSPavel Dovgalyuk static void replay_enable(const char *fname, int mode)
2477615936eSPavel Dovgalyuk {
2487615936eSPavel Dovgalyuk     const char *fmode = NULL;
2497615936eSPavel Dovgalyuk     assert(!replay_file);
2507615936eSPavel Dovgalyuk 
2517615936eSPavel Dovgalyuk     switch (mode) {
2527615936eSPavel Dovgalyuk     case REPLAY_MODE_RECORD:
2537615936eSPavel Dovgalyuk         fmode = "wb";
2547615936eSPavel Dovgalyuk         break;
2557615936eSPavel Dovgalyuk     case REPLAY_MODE_PLAY:
2567615936eSPavel Dovgalyuk         fmode = "rb";
2577615936eSPavel Dovgalyuk         break;
2587615936eSPavel Dovgalyuk     default:
2597615936eSPavel Dovgalyuk         fprintf(stderr, "Replay: internal error: invalid replay mode\n");
2607615936eSPavel Dovgalyuk         exit(1);
2617615936eSPavel Dovgalyuk     }
2627615936eSPavel Dovgalyuk 
2637615936eSPavel Dovgalyuk     atexit(replay_finish);
2647615936eSPavel Dovgalyuk 
2657615936eSPavel Dovgalyuk     replay_file = fopen(fname, fmode);
2667615936eSPavel Dovgalyuk     if (replay_file == NULL) {
2677615936eSPavel Dovgalyuk         fprintf(stderr, "Replay: open %s: %s\n", fname, strerror(errno));
2687615936eSPavel Dovgalyuk         exit(1);
2697615936eSPavel Dovgalyuk     }
2707615936eSPavel Dovgalyuk 
2717615936eSPavel Dovgalyuk     replay_filename = g_strdup(fname);
2727615936eSPavel Dovgalyuk     replay_mode = mode;
273d759c951SAlex Bennée     replay_mutex_init();
274d759c951SAlex Bennée 
275f186d64dSPavel Dovgalyuk     replay_state.data_kind = -1;
27613f26713SPavel Dovgalyuk     replay_state.instruction_count = 0;
27713f26713SPavel Dovgalyuk     replay_state.current_icount = 0;
278f186d64dSPavel Dovgalyuk     replay_state.has_unread_data = 0;
2797615936eSPavel Dovgalyuk 
2807615936eSPavel Dovgalyuk     /* skip file header for RECORD and check it for PLAY */
2817615936eSPavel Dovgalyuk     if (replay_mode == REPLAY_MODE_RECORD) {
2827615936eSPavel Dovgalyuk         fseek(replay_file, HEADER_SIZE, SEEK_SET);
2837615936eSPavel Dovgalyuk     } else if (replay_mode == REPLAY_MODE_PLAY) {
2847615936eSPavel Dovgalyuk         unsigned int version = replay_get_dword();
2857615936eSPavel Dovgalyuk         if (version != REPLAY_VERSION) {
2867615936eSPavel Dovgalyuk             fprintf(stderr, "Replay: invalid input log file version\n");
2877615936eSPavel Dovgalyuk             exit(1);
2887615936eSPavel Dovgalyuk         }
2897615936eSPavel Dovgalyuk         /* go to the beginning */
2907615936eSPavel Dovgalyuk         fseek(replay_file, HEADER_SIZE, SEEK_SET);
2917615936eSPavel Dovgalyuk         replay_fetch_data_kind();
2927615936eSPavel Dovgalyuk     }
2937615936eSPavel Dovgalyuk 
2947615936eSPavel Dovgalyuk     replay_init_events();
2957615936eSPavel Dovgalyuk }
2967615936eSPavel Dovgalyuk 
2977615936eSPavel Dovgalyuk void replay_configure(QemuOpts *opts)
2987615936eSPavel Dovgalyuk {
2997615936eSPavel Dovgalyuk     const char *fname;
3007615936eSPavel Dovgalyuk     const char *rr;
3017615936eSPavel Dovgalyuk     ReplayMode mode = REPLAY_MODE_NONE;
302890ad550SEduardo Habkost     Location loc;
303890ad550SEduardo Habkost 
304890ad550SEduardo Habkost     if (!opts) {
305890ad550SEduardo Habkost         return;
306890ad550SEduardo Habkost     }
307890ad550SEduardo Habkost 
308890ad550SEduardo Habkost     loc_push_none(&loc);
309890ad550SEduardo Habkost     qemu_opts_loc_restore(opts);
3107615936eSPavel Dovgalyuk 
3117615936eSPavel Dovgalyuk     rr = qemu_opt_get(opts, "rr");
3127615936eSPavel Dovgalyuk     if (!rr) {
3137615936eSPavel Dovgalyuk         /* Just enabling icount */
314d9d3aaeaSMarkus Armbruster         goto out;
3157615936eSPavel Dovgalyuk     } else if (!strcmp(rr, "record")) {
3167615936eSPavel Dovgalyuk         mode = REPLAY_MODE_RECORD;
3177615936eSPavel Dovgalyuk     } else if (!strcmp(rr, "replay")) {
3187615936eSPavel Dovgalyuk         mode = REPLAY_MODE_PLAY;
3197615936eSPavel Dovgalyuk     } else {
3207615936eSPavel Dovgalyuk         error_report("Invalid icount rr option: %s", rr);
3217615936eSPavel Dovgalyuk         exit(1);
3227615936eSPavel Dovgalyuk     }
3237615936eSPavel Dovgalyuk 
3247615936eSPavel Dovgalyuk     fname = qemu_opt_get(opts, "rrfile");
3257615936eSPavel Dovgalyuk     if (!fname) {
3267615936eSPavel Dovgalyuk         error_report("File name not specified for replay");
3277615936eSPavel Dovgalyuk         exit(1);
3287615936eSPavel Dovgalyuk     }
3297615936eSPavel Dovgalyuk 
3309c2037d0SPavel Dovgalyuk     replay_snapshot = g_strdup(qemu_opt_get(opts, "rrsnapshot"));
331306e196fSPavel Dovgalyuk     replay_vmstate_register();
3327615936eSPavel Dovgalyuk     replay_enable(fname, mode);
333890ad550SEduardo Habkost 
334d9d3aaeaSMarkus Armbruster out:
335890ad550SEduardo Habkost     loc_pop(&loc);
3367615936eSPavel Dovgalyuk }
3377615936eSPavel Dovgalyuk 
3387615936eSPavel Dovgalyuk void replay_start(void)
3397615936eSPavel Dovgalyuk {
3407615936eSPavel Dovgalyuk     if (replay_mode == REPLAY_MODE_NONE) {
3417615936eSPavel Dovgalyuk         return;
3427615936eSPavel Dovgalyuk     }
3437615936eSPavel Dovgalyuk 
3440194749aSPavel Dovgalyuk     if (replay_blockers) {
345c29b77f9SMarkus Armbruster         error_reportf_err(replay_blockers->data, "Record/replay: ");
3460194749aSPavel Dovgalyuk         exit(1);
3470194749aSPavel Dovgalyuk     }
3484c27b859SPavel Dovgalyuk     if (!use_icount) {
3494c27b859SPavel Dovgalyuk         error_report("Please enable icount to use record/replay");
3504c27b859SPavel Dovgalyuk         exit(1);
3514c27b859SPavel Dovgalyuk     }
3520194749aSPavel Dovgalyuk 
3537615936eSPavel Dovgalyuk     /* Timer for snapshotting will be set up here. */
3547615936eSPavel Dovgalyuk 
3557615936eSPavel Dovgalyuk     replay_enable_events();
3567615936eSPavel Dovgalyuk }
3577615936eSPavel Dovgalyuk 
3587615936eSPavel Dovgalyuk void replay_finish(void)
3597615936eSPavel Dovgalyuk {
3607615936eSPavel Dovgalyuk     if (replay_mode == REPLAY_MODE_NONE) {
3617615936eSPavel Dovgalyuk         return;
3627615936eSPavel Dovgalyuk     }
3637615936eSPavel Dovgalyuk 
3647615936eSPavel Dovgalyuk     replay_save_instructions();
3657615936eSPavel Dovgalyuk 
3667615936eSPavel Dovgalyuk     /* finalize the file */
3677615936eSPavel Dovgalyuk     if (replay_file) {
3687615936eSPavel Dovgalyuk         if (replay_mode == REPLAY_MODE_RECORD) {
369ed5d7ff3SPavel Dovgalyuk             /*
370ed5d7ff3SPavel Dovgalyuk              * Can't do it in the signal handler, therefore
371ed5d7ff3SPavel Dovgalyuk              * add shutdown event here for the case of Ctrl-C.
372ed5d7ff3SPavel Dovgalyuk              */
373ed5d7ff3SPavel Dovgalyuk             replay_shutdown_request(SHUTDOWN_CAUSE_HOST_SIGNAL);
3747615936eSPavel Dovgalyuk             /* write end event */
3757615936eSPavel Dovgalyuk             replay_put_event(EVENT_END);
3767615936eSPavel Dovgalyuk 
3777615936eSPavel Dovgalyuk             /* write header */
3787615936eSPavel Dovgalyuk             fseek(replay_file, 0, SEEK_SET);
3797615936eSPavel Dovgalyuk             replay_put_dword(REPLAY_VERSION);
3807615936eSPavel Dovgalyuk         }
3817615936eSPavel Dovgalyuk 
3827615936eSPavel Dovgalyuk         fclose(replay_file);
3837615936eSPavel Dovgalyuk         replay_file = NULL;
3847615936eSPavel Dovgalyuk     }
3857615936eSPavel Dovgalyuk     if (replay_filename) {
3867615936eSPavel Dovgalyuk         g_free(replay_filename);
3877615936eSPavel Dovgalyuk         replay_filename = NULL;
3887615936eSPavel Dovgalyuk     }
3897615936eSPavel Dovgalyuk 
3909c2037d0SPavel Dovgalyuk     g_free(replay_snapshot);
3919c2037d0SPavel Dovgalyuk     replay_snapshot = NULL;
3929c2037d0SPavel Dovgalyuk 
393ae25dccbSPavel Dovgalyuk     replay_mode = REPLAY_MODE_NONE;
394ae25dccbSPavel Dovgalyuk 
3957615936eSPavel Dovgalyuk     replay_finish_events();
3967615936eSPavel Dovgalyuk }
3970194749aSPavel Dovgalyuk 
3980194749aSPavel Dovgalyuk void replay_add_blocker(Error *reason)
3990194749aSPavel Dovgalyuk {
4000194749aSPavel Dovgalyuk     replay_blockers = g_slist_prepend(replay_blockers, reason);
4010194749aSPavel Dovgalyuk }
402