mirror of https://github.com/LIJI32/SameBoy.git
Add thread safety assertions in debug
This commit is contained in:
parent
a040b1b395
commit
30e2a7b7e4
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
50
Core/gb.c
50
Core/gb.c
|
@ -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
|
||||
|
|
25
Core/gb.h
25
Core/gb.h
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue