Add thread safety assertions in debug

This commit is contained in:
Lior Halphon 2023-02-05 19:51:11 +02:00
parent a040b1b395
commit 30e2a7b7e4
8 changed files with 112 additions and 10 deletions

View File

@ -6,6 +6,15 @@
typedef uint8_t (*GB_camera_get_pixel_callback_t)(GB_gameboy_t *gb, uint8_t x, uint8_t y);
typedef void (*GB_camera_update_request_callback_t)(GB_gameboy_t *gb);
void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback);
void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback);
void GB_camera_updated(GB_gameboy_t *gb);
#ifdef GB_INTERNAL
internal uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr);
internal void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value);
internal uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr);
enum {
GB_CAMERA_SHOOT_AND_1D_FLAGS = 0,
GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS = 1,
@ -15,15 +24,6 @@ enum {
GB_CAMERA_DITHERING_PATTERN_START = 6,
GB_CAMERA_DITHERING_PATTERN_END = 0x35,
};
uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr);
void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback);
void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback);
void GB_camera_updated(GB_gameboy_t *gb);
void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value);
uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr);
#endif
#endif

View File

@ -61,6 +61,8 @@ void GB_set_cheats_enabled(GB_gameboy_t *gb, bool enabled)
void GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
GB_cheat_t *cheat = malloc(sizeof(*cheat));
cheat->address = address;
cheat->bank = bank;
@ -91,8 +93,11 @@ const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size)
*size = gb->cheat_count;
return (void *)gb->cheats;
}
void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
for (unsigned i = 0; i < gb->cheat_count; i++) {
if (gb->cheats[i] == cheat) {
gb->cheats[i] = gb->cheats[--gb->cheat_count];
@ -127,6 +132,8 @@ void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat)
bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
uint8_t dummy;
/* GameShark */
{
@ -186,6 +193,8 @@ bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *descriptio
void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *_cheat, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
GB_cheat_t *cheat = NULL;
for (unsigned i = 0; i < gb->cheat_count; i++) {
if (gb->cheats[i] == _cheat) {
@ -242,6 +251,8 @@ void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *_cheat, const char *des
void GB_load_cheats(GB_gameboy_t *gb, const char *path)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
FILE *f = fopen(path, "rb");
if (!f) {
return;

View File

@ -2277,6 +2277,8 @@ void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr)
/* Returns true if debugger waits for more commands */
bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
while (*input == ' ') {
input++;
}
@ -2598,6 +2600,8 @@ const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr)
/* The public version of debugger_evaluate */
bool GB_debugger_evaluate(GB_gameboy_t *gb, const char *string, uint16_t *result, uint16_t *result_bank)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
bool error = false;
value_t value = debugger_evaluate(gb, string, strlen(string), &error, NULL, NULL);
if (result) {

View File

@ -197,6 +197,7 @@ GB_model_t GB_get_model(GB_gameboy_t *gb)
void GB_free(GB_gameboy_t *gb)
{
GB_ASSERT_NOT_RUNNING(gb)
gb->magic = 0;
if (gb->ram) {
free(gb->ram);
@ -321,6 +322,8 @@ static size_t rounded_rom_size(size_t size)
int GB_load_rom(GB_gameboy_t *gb, const char *path)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
FILE *f = fopen(path, "rb");
if (!f) {
GB_log(gb, "Could not open ROM: %s.\n", strerror(errno));
@ -412,6 +415,8 @@ void GB_gbs_switch_track(GB_gameboy_t *gb, uint8_t track)
int GB_load_gbs_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size, GB_gbs_info_t *info)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
if (size < sizeof(gb->gbs_header)) {
GB_log(gb, "Not a valid GBS file.\n");
return -1;
@ -488,6 +493,8 @@ int GB_load_gbs_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size
int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
FILE *f = fopen(path, "rb");
if (!f) {
GB_log(gb, "Could not open GBS: %s.\n", strerror(errno));
@ -507,6 +514,8 @@ int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info)
int GB_load_isx(GB_gameboy_t *gb, const char *path)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
FILE *f = fopen(path, "rb");
if (!f) {
GB_log(gb, "Could not open ISX file: %s.\n", strerror(errno));
@ -723,6 +732,8 @@ error:
void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
gb->rom_size = rounded_rom_size(size);
if (gb->rom) {
free(gb->rom);
@ -857,6 +868,8 @@ int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size)
int GB_save_battery(GB_gameboy_t *gb, const char *path)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
if (!gb->cartridge_type->has_battery) return 0; // Nothing to save.
if (gb->cartridge_type->mbc_type == GB_TPP1 && !(gb->rom[0x153] & 8)) return 0; // Nothing to save.
if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */
@ -929,6 +942,8 @@ static void load_tpp1_save_data(GB_gameboy_t *gb, const tpp1_rtc_save_t *data)
void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
memcpy(gb->mbc_ram, buffer, MIN(gb->mbc_ram_size, size));
if (size <= gb->mbc_ram_size) {
goto reset_rtc;
@ -1035,6 +1050,8 @@ exit:
/* Loading will silently stop if the format is incomplete */
void GB_load_battery(GB_gameboy_t *gb, const char *path)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
FILE *f = fopen(path, "rb");
if (!f) {
return;
@ -1143,6 +1160,7 @@ exit:
unsigned GB_run(GB_gameboy_t *gb)
{
GB_ASSERT_NOT_RUNNING(gb)
gb->vblank_just_occured = false;
if (gb->sgb && gb->sgb->intro_animation < 96) {
@ -1152,17 +1170,24 @@ unsigned GB_run(GB_gameboy_t *gb)
we just halt the CPU (with hacky code) until the correct time.
This ensures the Nintendo logo doesn't flash on screen, and
the game does "run in background" while the animation is playing. */
GB_set_running_thread(gb);
GB_display_run(gb, 228, true);
GB_clear_running_thread(gb);
gb->cycles_since_last_sync += 228;
return 228;
}
GB_debugger_run(gb);
gb->cycles_since_run = 0;
GB_set_running_thread(gb);
GB_cpu_run(gb);
GB_clear_running_thread(gb);
if (gb->vblank_just_occured) {
GB_debugger_handle_async_commands(gb);
GB_set_running_thread(gb);
GB_rewind_push(gb);
GB_clear_running_thread(gb);
}
if (!(gb->io_registers[GB_IO_IF] & 0x10) && (gb->io_registers[GB_IO_JOYP] & 0x30) != 0x30) {
gb->joyp_accessed = true;
@ -1192,6 +1217,7 @@ uint64_t GB_run_frame(GB_gameboy_t *gb)
void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
gb->screen = output;
}
@ -1751,16 +1777,19 @@ static void GB_reset_internal(GB_gameboy_t *gb, bool quick)
void GB_reset(GB_gameboy_t *gb)
{
GB_ASSERT_NOT_RUNNING(gb)
GB_reset_internal(gb, false);
}
void GB_quick_reset(GB_gameboy_t *gb)
{
GB_ASSERT_NOT_RUNNING(gb)
GB_reset_internal(gb, true);
}
void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model)
{
GB_ASSERT_NOT_RUNNING(gb)
gb->model = model;
if (GB_is_cgb(gb)) {
gb->ram = realloc(gb->ram, gb->ram_size = 0x1000 * 8);
@ -2053,3 +2082,24 @@ uint32_t GB_get_rom_crc32(GB_gameboy_t *gb)
}
return ~ret;
}
#ifdef GB_CONTEXT_SAFETY
void *GB_get_thread_id(void)
{
// POSIX requires errno to be thread local, making errno's address unique per thread
return &errno;
}
void GB_set_running_thread(GB_gameboy_t *gb)
{
GB_ASSERT_NOT_RUNNING(gb)
gb->running_thread_id = GB_get_thread_id();
}
void GB_clear_running_thread(GB_gameboy_t *gb)
{
assert(gb->running_thread_id == GB_get_thread_id());
gb->running_thread_id = NULL;
}
#endif

View File

@ -813,6 +813,9 @@ struct GB_gameboy_internal_s {
bool returned_open_bus;
uint16_t addr_for_hdma_conflict;
/* Thread safety (debug only) */
void *running_thread_id;
GB_gbs_header_t gbs_header;
)
};
@ -986,4 +989,26 @@ internal void GB_borrow_sgb_border(GB_gameboy_t *gb);
internal void GB_update_clock_rate(GB_gameboy_t *gb);
#endif
#ifdef GB_INTERNAL
#ifndef NDEBUG
#define GB_CONTEXT_SAFETY
#endif
#ifdef GB_CONTEXT_SAFETY
#include <assert.h>
internal void *GB_get_thread_id(void);
internal void GB_set_running_thread(GB_gameboy_t *gb);
internal void GB_clear_running_thread(GB_gameboy_t *gb);
#define GB_ASSERT_NOT_RUNNING(gb) if (gb->running_thread_id) {GB_log(gb, "Function %s must not be called in a running context.\n", __FUNCTION__); assert(!gb->running_thread_id);}
#define GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb) if (gb->running_thread_id && gb->running_thread_id != GB_get_thread_id()) {GB_log(gb, "Function %s must not be called while running in another thread.\n", __FUNCTION__); assert(!gb->running_thread_id || gb->running_thread_id == GB_get_thread_id());}
#else
#define GB_ASSERT_NOT_RUNNING(gb)
#define GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
#define GB_set_running_thread(gb)
#define GB_clear_running_thread(gb)
#endif
#endif
#endif

View File

@ -755,6 +755,8 @@ void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t cal
uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
if (unlikely(gb->n_watchpoints)) {
GB_debugger_test_read_watchpoint(gb, addr);
}
@ -1730,6 +1732,8 @@ void GB_set_write_memory_callback(GB_gameboy_t *gb, GB_write_memory_callback_t c
void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
if (unlikely(gb->n_watchpoints)) {
GB_debugger_test_write_watchpoint(gb, addr, value);
}

View File

@ -160,6 +160,8 @@ void GB_rewind_push(GB_gameboy_t *gb)
bool GB_rewind_pop(GB_gameboy_t *gb)
{
GB_ASSERT_NOT_RUNNING(gb)
if (!gb->rewind_sequences || !gb->rewind_sequences[gb->rewind_pos].key_state) {
return false;
}
@ -187,6 +189,8 @@ bool GB_rewind_pop(GB_gameboy_t *gb)
void GB_rewind_reset(GB_gameboy_t *gb)
{
GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
if (!gb->rewind_sequences) return;
for (unsigned i = 0; i < gb->rewind_buffer_length; i++) {
if (gb->rewind_sequences[i].key_state) {

View File

@ -834,6 +834,7 @@ error:
int GB_save_state(GB_gameboy_t *gb, const char *path)
{
GB_ASSERT_NOT_RUNNING(gb)
FILE *f = fopen(path, "wb");
if (!f) {
GB_log(gb, "Could not open save state: %s.\n", strerror(errno));
@ -852,6 +853,7 @@ int GB_save_state(GB_gameboy_t *gb, const char *path)
void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer)
{
GB_ASSERT_NOT_RUNNING(gb)
virtual_file_t file = {
.write = buffer_write,
.seek = buffer_seek,
@ -1354,6 +1356,7 @@ static int load_state_internal(GB_gameboy_t *gb, virtual_file_t *file)
int GB_load_state(GB_gameboy_t *gb, const char *path)
{
GB_ASSERT_NOT_RUNNING(gb)
FILE *f = fopen(path, "rb");
if (!f) {
GB_log(gb, "Could not open save state: %s.\n", strerror(errno));
@ -1372,6 +1375,7 @@ int GB_load_state(GB_gameboy_t *gb, const char *path)
int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length)
{
GB_ASSERT_NOT_RUNNING(gb)
virtual_file_t file = {
.read = buffer_read,
.seek = buffer_seek,