xref: /qemu/replay/replay.c (revision 66eb7825d0bd84a870a054fb208fe765317109fa)
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"
1526bc60acSPavel Dovgalyuk #include "replay-internal.h"
1626bc60acSPavel Dovgalyuk #include "qemu/timer.h"
178b427044SPavel Dovgalyuk #include "qemu/main-loop.h"
18922a01a0SMarkus Armbruster #include "qemu/option.h"
19d2528bdcSPaolo Bonzini #include "sysemu/cpus.h"
20b60c48a7SPavel Dovgalyuk #include "sysemu/sysemu.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. */
2580be169cSAlex Bennée #define REPLAY_VERSION              0xe02007
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 */
4226bc60acSPavel Dovgalyuk     if (replay_state.instructions_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) {
48f186d64dSPavel Dovgalyuk         if (event == replay_state.data_kind) {
4926bc60acSPavel Dovgalyuk             res = true;
5026bc60acSPavel Dovgalyuk         }
51f186d64dSPavel Dovgalyuk         switch (replay_state.data_kind) {
52802f045aSEric Blake         case EVENT_SHUTDOWN ... EVENT_SHUTDOWN_LAST:
53b60c48a7SPavel Dovgalyuk             replay_finish_event();
54cf83f140SEric Blake             qemu_system_shutdown_request(replay_state.data_kind -
55cf83f140SEric Blake                                          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 
6526bc60acSPavel Dovgalyuk uint64_t replay_get_current_step(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)) {
758b427044SPavel Dovgalyuk         res = replay_state.instructions_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());
858b427044SPavel Dovgalyuk         if (replay_state.instructions_count > 0) {
868b427044SPavel Dovgalyuk             int count = (int)(replay_get_current_step()
878b427044SPavel Dovgalyuk                               - replay_state.current_step);
88982263ceSAlex Bennée 
89982263ceSAlex Bennée             /* Time can only go forward */
90982263ceSAlex Bennée             assert(count >= 0);
91982263ceSAlex Bennée 
928b427044SPavel Dovgalyuk             replay_state.instructions_count -= count;
938b427044SPavel Dovgalyuk             replay_state.current_step += count;
948b427044SPavel Dovgalyuk             if (replay_state.instructions_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;
179*66eb7825SPavel 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 
186*66eb7825SPavel Dovgalyuk     if (in_checkpoint) {
187*66eb7825SPavel Dovgalyuk         /* If we are already in checkpoint, then there is no need
188*66eb7825SPavel Dovgalyuk            for additional synchronization.
189*66eb7825SPavel Dovgalyuk            Recursion occurs when HW event modifies timers.
190*66eb7825SPavel Dovgalyuk            Timer modification may invoke the checkpoint and
191*66eb7825SPavel Dovgalyuk            proceed to recursion. */
192*66eb7825SPavel Dovgalyuk         return true;
193*66eb7825SPavel Dovgalyuk     }
194*66eb7825SPavel Dovgalyuk     in_checkpoint = true;
195*66eb7825SPavel Dovgalyuk 
196*66eb7825SPavel 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);
2148bd7f71dSPavel Dovgalyuk         replay_save_events(checkpoint);
2158bd7f71dSPavel Dovgalyuk         res = true;
2168bd7f71dSPavel Dovgalyuk     }
2178bd7f71dSPavel Dovgalyuk out:
218*66eb7825SPavel Dovgalyuk     in_checkpoint = false;
2198bd7f71dSPavel Dovgalyuk     return res;
2208bd7f71dSPavel Dovgalyuk }
2217615936eSPavel Dovgalyuk 
2227615936eSPavel Dovgalyuk static void replay_enable(const char *fname, int mode)
2237615936eSPavel Dovgalyuk {
2247615936eSPavel Dovgalyuk     const char *fmode = NULL;
2257615936eSPavel Dovgalyuk     assert(!replay_file);
2267615936eSPavel Dovgalyuk 
2277615936eSPavel Dovgalyuk     switch (mode) {
2287615936eSPavel Dovgalyuk     case REPLAY_MODE_RECORD:
2297615936eSPavel Dovgalyuk         fmode = "wb";
2307615936eSPavel Dovgalyuk         break;
2317615936eSPavel Dovgalyuk     case REPLAY_MODE_PLAY:
2327615936eSPavel Dovgalyuk         fmode = "rb";
2337615936eSPavel Dovgalyuk         break;
2347615936eSPavel Dovgalyuk     default:
2357615936eSPavel Dovgalyuk         fprintf(stderr, "Replay: internal error: invalid replay mode\n");
2367615936eSPavel Dovgalyuk         exit(1);
2377615936eSPavel Dovgalyuk     }
2387615936eSPavel Dovgalyuk 
2397615936eSPavel Dovgalyuk     atexit(replay_finish);
2407615936eSPavel Dovgalyuk 
2417615936eSPavel Dovgalyuk     replay_file = fopen(fname, fmode);
2427615936eSPavel Dovgalyuk     if (replay_file == NULL) {
2437615936eSPavel Dovgalyuk         fprintf(stderr, "Replay: open %s: %s\n", fname, strerror(errno));
2447615936eSPavel Dovgalyuk         exit(1);
2457615936eSPavel Dovgalyuk     }
2467615936eSPavel Dovgalyuk 
2477615936eSPavel Dovgalyuk     replay_filename = g_strdup(fname);
2487615936eSPavel Dovgalyuk     replay_mode = mode;
249d759c951SAlex Bennée     replay_mutex_init();
250d759c951SAlex Bennée 
251f186d64dSPavel Dovgalyuk     replay_state.data_kind = -1;
2527615936eSPavel Dovgalyuk     replay_state.instructions_count = 0;
2537615936eSPavel Dovgalyuk     replay_state.current_step = 0;
254f186d64dSPavel Dovgalyuk     replay_state.has_unread_data = 0;
2557615936eSPavel Dovgalyuk 
2567615936eSPavel Dovgalyuk     /* skip file header for RECORD and check it for PLAY */
2577615936eSPavel Dovgalyuk     if (replay_mode == REPLAY_MODE_RECORD) {
2587615936eSPavel Dovgalyuk         fseek(replay_file, HEADER_SIZE, SEEK_SET);
2597615936eSPavel Dovgalyuk     } else if (replay_mode == REPLAY_MODE_PLAY) {
2607615936eSPavel Dovgalyuk         unsigned int version = replay_get_dword();
2617615936eSPavel Dovgalyuk         if (version != REPLAY_VERSION) {
2627615936eSPavel Dovgalyuk             fprintf(stderr, "Replay: invalid input log file version\n");
2637615936eSPavel Dovgalyuk             exit(1);
2647615936eSPavel Dovgalyuk         }
2657615936eSPavel Dovgalyuk         /* go to the beginning */
2667615936eSPavel Dovgalyuk         fseek(replay_file, HEADER_SIZE, SEEK_SET);
2677615936eSPavel Dovgalyuk         replay_fetch_data_kind();
2687615936eSPavel Dovgalyuk     }
2697615936eSPavel Dovgalyuk 
2707615936eSPavel Dovgalyuk     replay_init_events();
2717615936eSPavel Dovgalyuk }
2727615936eSPavel Dovgalyuk 
2737615936eSPavel Dovgalyuk void replay_configure(QemuOpts *opts)
2747615936eSPavel Dovgalyuk {
2757615936eSPavel Dovgalyuk     const char *fname;
2767615936eSPavel Dovgalyuk     const char *rr;
2777615936eSPavel Dovgalyuk     ReplayMode mode = REPLAY_MODE_NONE;
278890ad550SEduardo Habkost     Location loc;
279890ad550SEduardo Habkost 
280890ad550SEduardo Habkost     if (!opts) {
281890ad550SEduardo Habkost         return;
282890ad550SEduardo Habkost     }
283890ad550SEduardo Habkost 
284890ad550SEduardo Habkost     loc_push_none(&loc);
285890ad550SEduardo Habkost     qemu_opts_loc_restore(opts);
2867615936eSPavel Dovgalyuk 
2877615936eSPavel Dovgalyuk     rr = qemu_opt_get(opts, "rr");
2887615936eSPavel Dovgalyuk     if (!rr) {
2897615936eSPavel Dovgalyuk         /* Just enabling icount */
290d9d3aaeaSMarkus Armbruster         goto out;
2917615936eSPavel Dovgalyuk     } else if (!strcmp(rr, "record")) {
2927615936eSPavel Dovgalyuk         mode = REPLAY_MODE_RECORD;
2937615936eSPavel Dovgalyuk     } else if (!strcmp(rr, "replay")) {
2947615936eSPavel Dovgalyuk         mode = REPLAY_MODE_PLAY;
2957615936eSPavel Dovgalyuk     } else {
2967615936eSPavel Dovgalyuk         error_report("Invalid icount rr option: %s", rr);
2977615936eSPavel Dovgalyuk         exit(1);
2987615936eSPavel Dovgalyuk     }
2997615936eSPavel Dovgalyuk 
3007615936eSPavel Dovgalyuk     fname = qemu_opt_get(opts, "rrfile");
3017615936eSPavel Dovgalyuk     if (!fname) {
3027615936eSPavel Dovgalyuk         error_report("File name not specified for replay");
3037615936eSPavel Dovgalyuk         exit(1);
3047615936eSPavel Dovgalyuk     }
3057615936eSPavel Dovgalyuk 
3069c2037d0SPavel Dovgalyuk     replay_snapshot = g_strdup(qemu_opt_get(opts, "rrsnapshot"));
307306e196fSPavel Dovgalyuk     replay_vmstate_register();
3087615936eSPavel Dovgalyuk     replay_enable(fname, mode);
309890ad550SEduardo Habkost 
310d9d3aaeaSMarkus Armbruster out:
311890ad550SEduardo Habkost     loc_pop(&loc);
3127615936eSPavel Dovgalyuk }
3137615936eSPavel Dovgalyuk 
3147615936eSPavel Dovgalyuk void replay_start(void)
3157615936eSPavel Dovgalyuk {
3167615936eSPavel Dovgalyuk     if (replay_mode == REPLAY_MODE_NONE) {
3177615936eSPavel Dovgalyuk         return;
3187615936eSPavel Dovgalyuk     }
3197615936eSPavel Dovgalyuk 
3200194749aSPavel Dovgalyuk     if (replay_blockers) {
321c29b77f9SMarkus Armbruster         error_reportf_err(replay_blockers->data, "Record/replay: ");
3220194749aSPavel Dovgalyuk         exit(1);
3230194749aSPavel Dovgalyuk     }
3244c27b859SPavel Dovgalyuk     if (!use_icount) {
3254c27b859SPavel Dovgalyuk         error_report("Please enable icount to use record/replay");
3264c27b859SPavel Dovgalyuk         exit(1);
3274c27b859SPavel Dovgalyuk     }
3280194749aSPavel Dovgalyuk 
3297615936eSPavel Dovgalyuk     /* Timer for snapshotting will be set up here. */
3307615936eSPavel Dovgalyuk 
3317615936eSPavel Dovgalyuk     replay_enable_events();
3327615936eSPavel Dovgalyuk }
3337615936eSPavel Dovgalyuk 
3347615936eSPavel Dovgalyuk void replay_finish(void)
3357615936eSPavel Dovgalyuk {
3367615936eSPavel Dovgalyuk     if (replay_mode == REPLAY_MODE_NONE) {
3377615936eSPavel Dovgalyuk         return;
3387615936eSPavel Dovgalyuk     }
3397615936eSPavel Dovgalyuk 
3407615936eSPavel Dovgalyuk     replay_save_instructions();
3417615936eSPavel Dovgalyuk 
3427615936eSPavel Dovgalyuk     /* finalize the file */
3437615936eSPavel Dovgalyuk     if (replay_file) {
3447615936eSPavel Dovgalyuk         if (replay_mode == REPLAY_MODE_RECORD) {
3457615936eSPavel Dovgalyuk             /* write end event */
3467615936eSPavel Dovgalyuk             replay_put_event(EVENT_END);
3477615936eSPavel Dovgalyuk 
3487615936eSPavel Dovgalyuk             /* write header */
3497615936eSPavel Dovgalyuk             fseek(replay_file, 0, SEEK_SET);
3507615936eSPavel Dovgalyuk             replay_put_dword(REPLAY_VERSION);
3517615936eSPavel Dovgalyuk         }
3527615936eSPavel Dovgalyuk 
3537615936eSPavel Dovgalyuk         fclose(replay_file);
3547615936eSPavel Dovgalyuk         replay_file = NULL;
3557615936eSPavel Dovgalyuk     }
3567615936eSPavel Dovgalyuk     if (replay_filename) {
3577615936eSPavel Dovgalyuk         g_free(replay_filename);
3587615936eSPavel Dovgalyuk         replay_filename = NULL;
3597615936eSPavel Dovgalyuk     }
3607615936eSPavel Dovgalyuk 
3619c2037d0SPavel Dovgalyuk     g_free(replay_snapshot);
3629c2037d0SPavel Dovgalyuk     replay_snapshot = NULL;
3639c2037d0SPavel Dovgalyuk 
3647615936eSPavel Dovgalyuk     replay_finish_events();
3657615936eSPavel Dovgalyuk }
3660194749aSPavel Dovgalyuk 
3670194749aSPavel Dovgalyuk void replay_add_blocker(Error *reason)
3680194749aSPavel Dovgalyuk {
3690194749aSPavel Dovgalyuk     replay_blockers = g_slist_prepend(replay_blockers, reason);
3700194749aSPavel Dovgalyuk }
371