Skip to content

Commit 93bedab

Browse files
committed
Fix rewind buffer corruption bug
`RewindState` has a `RewindResult` which it checks when it is going to truncat the rewind buffer (after a successful rewind.) If the `RewindResult`'s info pointer is not set, `rewind_truncate_to` assumes that the rewind was not successful, so it doesn't truncate. The bug was that a local `RewindResult` was used instead of the one in `RewindState`, so the info pointer was never set, so the rewind buffer was never truncated. After a rewind, `rewind_append` would append states that were out of order. This would break the binary search of the rewind info. To find the bug, I ended up adding a sanity check function for the rewind buffer. So I decided to keep it.
1 parent 0f70d54 commit 93bedab

File tree

3 files changed

+96
-8
lines changed

3 files changed

+96
-8
lines changed

src/host.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -315,12 +315,12 @@ void host_begin_rewind(Host* host) {
315315
Result host_rewind_to_cycles(Host* host, Cycles cycles) {
316316
assert(host->rewind_state.rewinding);
317317

318-
RewindResult result;
319-
CHECK(SUCCESS(rewind_to_cycles(host->rewind_buffer, cycles, &result)));
318+
RewindResult* result = &host->rewind_state.rewind_result;
319+
CHECK(SUCCESS(rewind_to_cycles(host->rewind_buffer, cycles, result)));
320320

321321
struct Emulator* e = host_get_emulator(host);
322-
CHECK(SUCCESS(emulator_read_state(e, &result.file_data)));
323-
assert(emulator_get_cycles(e) == result.info->cycles);
322+
CHECK(SUCCESS(emulator_read_state(e, &result->file_data)));
323+
assert(emulator_get_cycles(e) == result->info->cycles);
324324

325325
host->rewind_state.current =
326326
joypad_find_state(host->joypad_buffer, emulator_get_cycles(e));
@@ -343,9 +343,11 @@ void host_end_rewind(Host* host) {
343343
assert(host->rewind_state.rewinding);
344344

345345
if (host->rewind_state.rewind_result.info) {
346-
rewind_truncate_to(host->rewind_buffer, &host->rewind_state.rewind_result);
346+
struct Emulator* e = host_get_emulator(host);
347+
rewind_truncate_to(host->rewind_buffer, e,
348+
&host->rewind_state.rewind_result);
347349
joypad_truncate_to(host->joypad_buffer, host->rewind_state.current);
348-
host->last_cycles = emulator_get_cycles(host_get_emulator(host));
350+
host->last_cycles = emulator_get_cycles(e);
349351
}
350352

351353
memset(&host->rewind_state, 0, sizeof(host->rewind_state));

src/rewind.c

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
#include "emulator.h"
1313

14+
#define SANITY_CHECK 0
15+
1416
#define INVALID_CYCLES (~0ULL)
1517

1618
#define GET_CYCLES(x) ((x).cycles)
@@ -85,6 +87,8 @@
8587
} \
8688
} while (0)
8789

90+
static void rewind_sanity_check(RewindBuffer*, struct Emulator*);
91+
8892
RewindBuffer* rewind_new(const RewindInit* init, struct Emulator* e) {
8993
RewindBuffer* buffer = malloc(sizeof(RewindBuffer));
9094
ZERO_MEMORY(*buffer);
@@ -285,6 +289,8 @@ void rewind_append(RewindBuffer* buf, struct Emulator* e) {
285289
/* Update stats. */
286290
buf->total_kind_bytes[kind] += new_info->size;
287291
buf->total_uncompressed_bytes += buf->last_state.size;
292+
293+
rewind_sanity_check(buf, e);
288294
}
289295

290296
Result rewind_to_cycles(RewindBuffer* buf, Cycles cycles,
@@ -359,7 +365,8 @@ Result rewind_to_cycles(RewindBuffer* buf, Cycles cycles,
359365
return OK;
360366
}
361367

362-
void rewind_truncate_to(RewindBuffer* buffer, RewindResult* result) {
368+
void rewind_truncate_to(RewindBuffer* buffer, struct Emulator* e,
369+
RewindResult* result) {
363370
/* Remove data from rewind buffer that are now invalid. */
364371
int info_range_index = result->info_range_index;
365372
RewindInfo* info = result->info;
@@ -371,6 +378,8 @@ void rewind_truncate_to(RewindBuffer* buffer, RewindResult* result) {
371378
info_range[0].begin = info_range[0].end;
372379
data_range[0].end = data_range[0].begin;
373380
}
381+
382+
rewind_sanity_check(buffer, e);
374383
}
375384

376385
static Bool is_rewind_range_empty(RewindInfoRange* r) {
@@ -431,3 +440,80 @@ RewindStats rewind_get_stats(RewindBuffer* buffer) {
431440

432441
return stats;
433442
}
443+
444+
void rewind_sanity_check(RewindBuffer* buffer, struct Emulator* e) {
445+
#if SANITY_CHECK
446+
assert(buffer->data_range[0].begin <= buffer->data_range[0].end);
447+
assert(buffer->data_range[0].end <= buffer->data_range[1].begin);
448+
assert((void*)buffer->data_range[0].end <=
449+
(void*)buffer->info_range[1].begin);
450+
assert(buffer->info_range[1].end <= buffer->info_range[0].begin);
451+
assert(buffer->info_range[0].begin <= buffer->info_range[0].end);
452+
453+
Bool has_base = FALSE;
454+
FileData base;
455+
FileData diff;
456+
FileData temp;
457+
emulator_init_state_file_data(&base);
458+
emulator_init_state_file_data(&diff);
459+
emulator_init_state_file_data(&temp);
460+
461+
Result result = emulator_write_state(e, &temp);
462+
assert(SUCCESS(result));
463+
464+
void* last_data_end = NULL;
465+
Cycles last_cycles = 0;
466+
467+
int i;
468+
for (i = 0; i < 2; ++i) {
469+
RewindDataRange* data_range = &buffer->data_range[i];
470+
RewindInfoRange* info_range = &buffer->info_range[i];
471+
472+
RewindInfo* info;
473+
for (info = info_range->end - 1; info >= info_range->begin; --info) {
474+
void* data_end = info->data + info->size;
475+
if (last_data_end) {
476+
assert(last_data_end < (void*)data_end);
477+
}
478+
last_data_end = data_end;
479+
}
480+
}
481+
482+
for (i = 1; i >= 0; --i) {
483+
RewindDataRange* data_range = &buffer->data_range[i];
484+
RewindInfoRange* info_range = &buffer->info_range[i];
485+
486+
RewindInfo* info;
487+
for (info = info_range->end - 1; info >= info_range->begin; --info) {
488+
assert(last_cycles <= info->cycles);
489+
last_cycles = info->cycles;
490+
491+
FileData* fd = NULL;
492+
if (info->kind == RewindInfoKind_Base) {
493+
has_base = TRUE;
494+
decode_rle(info->data, info->size, base.data, base.data + base.size);
495+
fd = &base;
496+
} else {
497+
assert(info->kind == RewindInfoKind_Diff);
498+
if (has_base) {
499+
decode_diff(info->data, info->size, base.data, diff.data,
500+
diff.data + diff.size);
501+
fd = &diff;
502+
}
503+
}
504+
505+
if (fd) {
506+
emulator_read_state(e, fd);
507+
assert(info->cycles == emulator_get_cycles(e));
508+
}
509+
}
510+
}
511+
512+
result = emulator_read_state(e, &temp);
513+
assert(SUCCESS(result));
514+
515+
file_data_delete(&temp);
516+
file_data_delete(&diff);
517+
file_data_delete(&base);
518+
#endif /* SANITY_CHECK */
519+
}

src/rewind.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ RewindBuffer* rewind_new(const RewindInit*, struct Emulator*);
104104
void rewind_delete(RewindBuffer*);
105105
void rewind_append(RewindBuffer*, struct Emulator*);
106106
Result rewind_to_cycles(RewindBuffer*, Cycles, RewindResult*);
107-
void rewind_truncate_to(RewindBuffer*, RewindResult*);
107+
void rewind_truncate_to(RewindBuffer*, struct Emulator*, RewindResult*);
108108
Cycles rewind_get_oldest_cycles(RewindBuffer*);
109109
Cycles rewind_get_newest_cycles(RewindBuffer*);
110110
RewindStats rewind_get_stats(RewindBuffer*);

0 commit comments

Comments
 (0)