diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 3de36d2bb5..ee7fa23b40 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -27,6 +27,8 @@ "F3DEX_GBI_2=1", "F3DZEX_NON_GBI_2=1", "F3DEX_GBI_SHARED=1", + "DEBUG_EXPORT_SYMBOLS=1", + "DEBUG_FULL_STACK_TRACE=1", "LIBPL=1" ], "compilerPath": "/usr/bin/mips-linux-gnu-gcc", diff --git a/Makefile b/Makefile index adac8a6b6e..2b9c2cd80f 100644 --- a/Makefile +++ b/Makefile @@ -90,7 +90,30 @@ endif # FIXLIGHTS - converts light objects to light color commands for assets, needed for vanilla-style lighting FIXLIGHTS ?= 1 -DEBUG_MAP_STACKTRACE_FLAG := -D DEBUG_MAP_STACKTRACE +#---------------- +# Debug Switches - Comment these out to remove the functionality, then run `make clean` +#---------------- +# Export symbols and stack trace data for crash screen +DEBUG_EXPORT_SYMBOLS := 1 +# Crash Screen: enable symbol-assisted stack trace. Adds a bit more code to (somewhat) intelligently walk the stack. +# If disabled, stack trace will just dump the memory at the stack pointer, from which one can cross-reference with the function map. +DEBUG_FULL_STACK_TRACE := 1 +# Include line data, at the cost of a significantly longer link time and a few extra megabytes of ROM +DEBUG_EXPORT_ALL_LINES := 0 + +ifeq ($(DEBUG_EXPORT_SYMBOLS),1) + ifeq ($(DEBUG_EXPORT_ALL_LINES),1) + DEBUG_EXPORT_ALL_LINES_FLAG += --all-lines + DEFINES += DEBUG_EXPORT_ALL_LINES=1 + endif + DEFINES += DEBUG_EXPORT_SYMBOLS=1 + GENERATE_DWARF := -g1 -gdwarf-4 + DEFAULT_OPT_FLAGS += $(GENERATE_DWARF) + SYMBOL_TABLE = $(BUILD_DIR)/sm64.sym.o +endif +ifeq ($(DEBUG_FULL_STACK_TRACE),1) + DEFINES += DEBUG_FULL_STACK_TRACE=1 +endif TARGET := sm64 @@ -135,7 +158,7 @@ endif #==============================================================================# # Default non-gcc opt flags -DEFAULT_OPT_FLAGS = -Os -ffinite-math-only -fno-signed-zeros -fno-math-errno +DEFAULT_OPT_FLAGS += -Os -ffinite-math-only -fno-signed-zeros -fno-math-errno # Note: -fno-associative-math is used here to suppress warnings, ideally we would enable this as an optimization but # this conflicts with -ftrapping-math apparently. # TODO: Figure out how to allow -fassociative-math to be enabled @@ -360,7 +383,7 @@ ACTOR_DIR := actors LEVEL_DIRS := $(patsubst levels/%,%,$(dir $(wildcard levels/*/header.h))) # Directories containing source files -SRC_DIRS += src src/boot src/boot/deflate src/game src/engine src/audio src/menu src/buffers lib/librtc actors levels bin data assets asm lib sound +SRC_DIRS += src src/boot src/boot/deflate src/game src/engine src/audio src/menu src/buffers src/debugger lib/librtc actors levels bin data assets asm lib sound LIBZ_SRC_DIRS := src/libz GODDARD_SRC_DIRS := src/goddard src/goddard/dynlists BIN_DIRS := bin bin/$(VERSION) @@ -443,6 +466,7 @@ endif AR := $(CROSS)ar OBJDUMP := $(CROSS)objdump OBJCOPY := $(CROSS)objcopy +ADDR2LINE := addr2line ifeq ($(LD), tools/mips64-elf-ld) ifeq ($(shell ls -la tools/mips64-elf-ld | awk '{print $1}' | grep x),) @@ -624,8 +648,8 @@ patch: $(ROM) # Extra object file dependencies $(BUILD_DIR)/asm/ipl3.o: $(IPL3_RAW_FILES) -$(BUILD_DIR)/src/game/crash_screen.o: $(CRASH_TEXTURE_C_FILES) $(BUILD_DIR)/src/game/fasttext.o: $(FASTTEXT_TEXTURE_C_FILES) +$(BUILD_DIR)/src/debugger/crash_screen.o: $(CRASH_TEXTURE_C_FILES) $(BUILD_DIR)/src/game/version.o: $(BUILD_DIR)/src/game/version_data.h $(BUILD_DIR)/lib/aspMain.o: $(BUILD_DIR)/rsp/audio.bin $(SOUND_BIN_DIR)/sound_data.o: $(SOUND_BIN_DIR)/sound_data.ctl $(SOUND_BIN_DIR)/sound_data.tbl $(SOUND_BIN_DIR)/sequences.bin $(SOUND_BIN_DIR)/bank_sets @@ -660,13 +684,13 @@ else endif endif -$(BUILD_DIR)/src/usb/usb.o: OPT_FLAGS := -O0 +$(BUILD_DIR)/src/usb/usb.o: OPT_FLAGS := $(GENERATE_DWARF) -O0 $(BUILD_DIR)/src/usb/usb.o: CFLAGS += -Wno-unused-variable -Wno-sign-compare -Wno-unused-function -$(BUILD_DIR)/src/usb/debug.o: OPT_FLAGS := -O0 +$(BUILD_DIR)/src/usb/debug.o: OPT_FLAGS := $(GENERATE_DWARF) -O0 $(BUILD_DIR)/src/usb/debug.o: CFLAGS += -Wno-unused-parameter -Wno-maybe-uninitialized # File specific opt flags -$(BUILD_DIR)/src/audio/heap.o: OPT_FLAGS := -Os -fno-jump-tables -$(BUILD_DIR)/src/audio/synthesis.o: OPT_FLAGS := -Os -fno-jump-tables +$(BUILD_DIR)/src/audio/heap.o: OPT_FLAGS := $(GENERATE_DWARF) -Os -fno-jump-tables +$(BUILD_DIR)/src/audio/synthesis.o: OPT_FLAGS := $(GENERATE_DWARF) -Os -fno-jump-tables $(BUILD_DIR)/src/engine/surface_collision.o: OPT_FLAGS := $(COLLISION_OPT_FLAGS) $(BUILD_DIR)/src/engine/math_util.o: OPT_FLAGS := $(MATH_UTIL_OPT_FLAGS) @@ -677,7 +701,7 @@ $(BUILD_DIR)/src/game/rendering_graph_node.o: OPT_FLAGS := $(GRAPH_NODE_OPT_FLAG # $(info MATH_UTIL_OPT_FLAGS: $(MATH_UTIL_OPT_FLAGS)) # $(info GRAPH_NODE_OPT_FLAGS: $(GRAPH_NODE_OPT_FLAGS)) -ALL_DIRS := $(BUILD_DIR) $(addprefix $(BUILD_DIR)/,$(SRC_DIRS) asm/debug $(GODDARD_SRC_DIRS) $(ULTRA_BIN_DIRS) $(BIN_DIRS) $(TEXTURE_DIRS) $(TEXT_DIRS) $(SOUND_SAMPLE_DIRS) $(addprefix levels/,$(LEVEL_DIRS)) rsp include) $(YAY0_DIR) $(addprefix $(YAY0_DIR)/,$(VERSION)) $(SOUND_BIN_DIR) $(SOUND_BIN_DIR)/sequences/$(VERSION) +ALL_DIRS := $(BUILD_DIR) $(addprefix $(BUILD_DIR)/,$(SRC_DIRS) $(GODDARD_SRC_DIRS) $(ULTRA_BIN_DIRS) $(BIN_DIRS) $(TEXTURE_DIRS) $(TEXT_DIRS) $(SOUND_SAMPLE_DIRS) $(addprefix levels/,$(LEVEL_DIRS)) rsp include) $(YAY0_DIR) $(addprefix $(YAY0_DIR)/,$(VERSION)) $(SOUND_BIN_DIR) $(SOUND_BIN_DIR)/sequences/$(VERSION) # Make sure build directory exists before compiling anything DUMMY != mkdir -p $(ALL_DIRS) @@ -877,7 +901,7 @@ $(BUILD_DIR)/rsp/%.bin $(BUILD_DIR)/rsp/%_data.bin: rsp/%.s # Run linker script through the C preprocessor $(BUILD_DIR)/$(LD_SCRIPT): $(LD_SCRIPT) $(BUILD_DIR)/goddard.txt $(call print,Preprocessing linker script:,$<,$@) - $(V)$(CPP) $(CPPFLAGS) -DBUILD_DIR=$(BUILD_DIR) -DULTRALIB=lib$(ULTRALIB) $(DEBUG_MAP_STACKTRACE_FLAG) -MMD -MP -MT $@ -MF $@.d -o $@ $< + $(V)$(CPP) $(CPPFLAGS) -DBUILD_DIR=$(BUILD_DIR) -DULTRALIB=lib$(ULTRALIB) -MMD -MP -MT $@ -MF $@.d -o $@ $< # Link libgoddard $(BUILD_DIR)/libgoddard.a: $(GODDARD_O_FILES) @@ -897,13 +921,14 @@ $(BUILD_DIR)/goddard.txt: $(BUILD_DIR)/sm64_prelim.elf $(call print,Getting Goddard size...) $(V)python3 tools/getGoddardSize.py $(BUILD_DIR)/sm64_prelim.map $(VERSION) -$(BUILD_DIR)/asm/debug/map.o: asm/debug/map.s $(BUILD_DIR)/sm64_prelim.elf - $(call print,Assembling:,$<,$@) - $(V)python3 tools/mapPacker.py $(BUILD_DIR)/sm64_prelim.elf $(BUILD_DIR)/bin/addr.bin $(BUILD_DIR)/bin/name.bin - $(V)$(CROSS)gcc -c $(ASMFLAGS) $(foreach i,$(INCLUDE_DIRS),-Wa,-I$(i)) -x assembler-with-cpp -MMD -MF $(BUILD_DIR)/$*.d -o $@ $< +$(SYMBOL_TABLE): $(BUILD_DIR)/sm64_prelim.elf + $(call print,Generating symbol table:,$(@F)) + $(V)tools/n64sym $(DEBUG_EXPORT_ALL_LINES_FLAG) --objdump $(OBJDUMP) --addr2line $(ADDR2LINE) $(BUILD_DIR)/sm64_prelim.elf + $(call print,Assembling:,$@) + $(LD) -r -b binary -o $@ $(BUILD_DIR)/sm64_prelim.sym # Link SM64 ELF file -$(ELF): $(BUILD_DIR)/sm64_prelim.elf $(BUILD_DIR)/asm/debug/map.o $(O_FILES) $(YAY0_OBJ_FILES) $(SEG_FILES) $(BUILD_DIR)/$(LD_SCRIPT) $(BUILD_DIR)/libgoddard.a +$(ELF): $(BUILD_DIR)/sm64_prelim.elf $(SYMBOL_TABLE) $(O_FILES) $(YAY0_OBJ_FILES) $(SEG_FILES) $(BUILD_DIR)/$(LD_SCRIPT) $(BUILD_DIR)/libgoddard.a @$(PRINT) "$(GREEN)Linking ELF file: $(BLUE)$@ $(NO_COL)\n" $(V)$(LD) --gc-sections -L $(BUILD_DIR) -T $(BUILD_DIR)/$(LD_SCRIPT) -T goddard.txt -Map $(BUILD_DIR)/sm64.$(VERSION).map --no-check-sections $(addprefix -R ,$(SEG_FILES)) -o $@ $(O_FILES) -L$(LIBS_DIR) -l$(ULTRALIB) -Llib $(LINK_LIBRARIES) -u sprintf -u osMapTLB -Llib/gcclib/$(LIBGCCDIR) -lgcc diff --git a/asm/debug/map.s b/asm/debug/map.s deleted file mode 100644 index fc88194287..0000000000 --- a/asm/debug/map.s +++ /dev/null @@ -1,17 +0,0 @@ -.include "macros.inc" -.section .data -.balign 16 -glabel gMapEntries -.incbin "bin/addr.bin" -glabel gMapEntryEnd - -.balign 16 -glabel gMapStrings -.incbin "bin/name.bin" -glabel gMapStringsEnd - -.balign 16 -glabel gMapEntrySize -.word (gMapEntryEnd - gMapEntries) / 4 -glabel gMapStringSize -.word (gMapStringsEnd - gMapStrings) diff --git a/asm/n64_assert.s b/asm/n64_assert.s index 33f991093f..eaedbaa355 100644 --- a/asm/n64_assert.s +++ b/asm/n64_assert.s @@ -1,19 +1,22 @@ .include "macros.inc" +#include "src/debugger/assert.h" .section .data glabel __n64Assert_Filename .skip 4 glabel __n64Assert_LineNum .skip 4 -glabel __n64Assert_Message +glabel __n64Assert_Condition .skip 4 +glabel __n64Assert_MessageBuf +.skip ASSERT_MESGBUF_SIZE .section .text glabel __n64Assert sw $a0, __n64Assert_Filename sw $a1, __n64Assert_LineNum -sw $a2, __n64Assert_Message +sw $a2, __n64Assert_Condition syscall nop diff --git a/include/config/config_debug.h b/include/config/config_debug.h index 7ed67cac93..f2ba64b02b 100644 --- a/include/config/config_debug.h +++ b/include/config/config_debug.h @@ -10,7 +10,7 @@ // #define DEBUG_ALL /** - * Disables all debug options (except PUPPYPRINT). + * Disables all debug options (except PUPPYPRINT and DEBUG_FULL_STACK_TRACE). */ // #define DISABLE_ALL diff --git a/include/segment_symbols.h b/include/segment_symbols.h index e664c475a8..89fddc01c4 100644 --- a/include/segment_symbols.h +++ b/include/segment_symbols.h @@ -51,10 +51,13 @@ DECLARE_SEGMENT(goddard) DECLARE_SEGMENT(framebuffers) DECLARE_SEGMENT(assets) extern u8 _goddardSegmentStart[]; +extern u8 _goddardSegmentTextEnd[]; extern u8 _goddardSegmentEnd[]; extern u8 _engineSegmentStart[]; +extern u8 _engineSegmentTextEnd[]; extern u8 _engineSegmentBssEnd[]; extern u8 _mainSegmentStart[]; +extern u8 _mainSegmentTextEnd[]; extern u8 _mainSegmentEnd[]; extern u8 _engineSegmentEnd[]; extern u8 _framebuffersSegmentBssStart[]; diff --git a/sm64.ld b/sm64.ld index 2639cc0dfe..421a1b1d55 100755 --- a/sm64.ld +++ b/sm64.ld @@ -146,11 +146,8 @@ SECTIONS #endif /* hardcoded symbols to satisfy preliminary link for map parser */ -#ifndef DEBUG_MAP_STACKTRACE +#ifndef DEBUG_EXPORT_SYMBOLS _mapDataSegmentRomStart = 0; - gMapEntries = 0; - gMapEntrySize = 0; - gMapStrings = 0; #endif BEGIN_SEG(main, .) SUBALIGN(16) @@ -167,6 +164,7 @@ SECTIONS BUILD_DIR/src/hvqm*.o(.text*); BUILD_DIR/src/usb*.o(.text*); BUILD_DIR/src/audio*.o(.text*); + BUILD_DIR/src/debugger*.o(.text*); #ifdef S2DEX_TEXT_ENGINE BUILD_DIR/src/s2d_engine*.o(.text*); #endif @@ -189,6 +187,7 @@ SECTIONS BUILD_DIR/asm/n64_assert.o(.*data*); BUILD_DIR/src/boot*.o(.*data*); BUILD_DIR/src/audio*.o(.*data*); + BUILD_DIR/src/debugger*.o(.*data*); #ifdef S2DEX_TEXT_ENGINE BUILD_DIR/src/s2d_engine*.o(.*data*); #endif @@ -207,6 +206,7 @@ SECTIONS BUILD_DIR/src/boot*.o(.rodata*); BUILD_DIR/src/usb*.o(.rodata*); BUILD_DIR/src/audio*.o(.rodata*); + BUILD_DIR/src/debugger*.o(.rodata*); #ifdef S2DEX_TEXT_ENGINE BUILD_DIR/src/s2d_engine*.o(.rodata*); #endif @@ -226,6 +226,7 @@ SECTIONS BUILD_DIR/src/hvqm*.o(.*bss*); BUILD_DIR/src/usb*.o(.*bss*); BUILD_DIR/src/audio*.o(.*bss*); + BUILD_DIR/src/debugger*.o(.*bss*); #ifdef S2DEX_TEXT_ENGINE BUILD_DIR/src/s2d_engine*.o(.*bss*); #endif @@ -524,15 +525,19 @@ SECTIONS END_SEG(capcom) #endif -#ifdef DEBUG_MAP_STACKTRACE - BEGIN_SEG(mapData, (RAM_END - 0x00100000)) { - KEEP(BUILD_DIR/asm/debug/map.o(.data*)); +#ifdef DEBUG_EXPORT_SYMBOLS + BEGIN_SEG(mapData, 0) ALIGN(16) { +#ifndef PRELIMINARY + KEEP(BUILD_DIR/sm64.sym.o(.data*)); +#endif } END_SEG(mapData) #endif /* DWARF debug sections. Symbols in the DWARF debugging sections are relative to the beginning of the section so we begin them at 0. */ + .debug_rnglists 0 : { *(.debug_rnglists) } + .debug_line_str 0 : { *(.debug_line_str) } /* DWARF 1 */ .debug 0 : { *(.debug) } .line 0 : { *(.line) } diff --git a/src/audio/external.c b/src/audio/external.c index af117f47d1..35dbcf6a4b 100644 --- a/src/audio/external.c +++ b/src/audio/external.c @@ -7,6 +7,7 @@ #include "external.h" #include "playback.h" #include "synthesis.h" +#include "debugger/assert.h" #include "game/debug.h" #include "game/main.h" #include "game/level_update.h" @@ -686,7 +687,7 @@ struct SPTask *create_next_audio_frame_task(void) { task->yield_data_ptr = NULL; task->yield_data_size = 0; - assert_args(writtenCmds <= gMaxAudioCmds, "Audio RSP pool exceeded: %d command(s) over!", (s32) gMaxAudioCmds - (s32) writtenCmds); + assertf(writtenCmds <= gMaxAudioCmds, "Audio RSP pool exceeded: %d command(s) over!", (s32) gMaxAudioCmds - (s32) writtenCmds); decrease_sample_dma_ttls(); @@ -698,7 +699,7 @@ struct SPTask *create_next_audio_frame_task(void) { * Called from threads: thread5_game_loop */ void play_sound(s32 soundBits, f32 *pos) { - assert(((soundBits & SOUNDARGS_MASK_SOUNDID) >> SOUNDARGS_SHIFT_SOUNDID) != 0xff, "Sfx tables do not support a sound id of 0xff!"); + assertf(((soundBits & SOUNDARGS_MASK_SOUNDID) >> SOUNDARGS_SHIFT_SOUNDID) != 0xff, "Sfx tables do not support a sound id of 0xff!"); sSoundRequests[sSoundRequestCount].soundBits = soundBits; sSoundRequests[sSoundRequestCount].position = pos; sSoundRequestCount++; diff --git a/src/audio/heap.c b/src/audio/heap.c index 75161172d8..adb30faea2 100644 --- a/src/audio/heap.c +++ b/src/audio/heap.c @@ -1056,9 +1056,9 @@ void init_reverb_us(s32 presetId) { #else if (gBetterReverbPresetValue >= gBetterReverbPresetCount) { #endif - aggress(gBetterReverbPresetCount > 0, "No BETTER_REVERB presets exist!"); + aggressf(gBetterReverbPresetCount > 0, "No BETTER_REVERB presets exist!"); - assert(gBetterReverbPresetValue < gBetterReverbPresetCount, "BETTER_REVERB preset value exceeds total number of available presets!"); + assertf(gBetterReverbPresetValue < gBetterReverbPresetCount, "BETTER_REVERB preset value exceeds total number of available presets!"); betterReverbPreset = &gBetterReverbSettings[0]; } diff --git a/src/audio/synthesis.c b/src/audio/synthesis.c index 10f027c01e..e1780c5e8b 100644 --- a/src/audio/synthesis.c +++ b/src/audio/synthesis.c @@ -206,7 +206,7 @@ void set_better_reverb_buffers(u32 *inputDelaysL, u32 *inputDelaysR) { } } - aggress_args(bufOffset * sizeof(s16) + BETTER_REVERB_PTR_SIZE <= BETTER_REVERB_SIZE, "BETTER_REVERB_SIZE is too small for this preset!\nSize should be at least 0x%06x!", bufOffset * sizeof(s16) + BETTER_REVERB_PTR_SIZE); + aggressf(bufOffset * sizeof(s16) + BETTER_REVERB_PTR_SIZE <= BETTER_REVERB_SIZE, "BETTER_REVERB_SIZE is too small for this preset!\nSize should be at least 0x%06x!", bufOffset * sizeof(s16) + BETTER_REVERB_PTR_SIZE); bzero(allpassIdx, sizeof(allpassIdx)); } diff --git a/src/boot/main.c b/src/boot/main.c index a239eecb1e..640526f625 100644 --- a/src/boot/main.c +++ b/src/boot/main.c @@ -6,6 +6,8 @@ #include "sm64.h" #include "audio/external.h" +#include "debugger/assert.h" +#include "debugger/crash_screen.h" #include "game/game_init.h" #include "game/debug.h" #include "game/memory.h" @@ -324,26 +326,25 @@ void alert_rcp_hung_up(void) { void check_stack_validity(void) { gIdleThreadStack[0]++; gIdleThreadStack[THREAD1_STACK - 1]++; - assert(gIdleThreadStack[0] == gIdleThreadStack[THREAD1_STACK - 1], "Thread 1 stack overflow.") + assertf(gIdleThreadStack[0] == gIdleThreadStack[THREAD1_STACK - 1], "Thread 1 stack overflow."); gThread3Stack[0]++; gThread3Stack[THREAD3_STACK - 1]++; - assert(gThread3Stack[0] == gThread3Stack[THREAD3_STACK - 1], "Thread 3 stack overflow.") + assertf(gThread3Stack[0] == gThread3Stack[THREAD3_STACK - 1], "Thread 3 stack overflow."); gThread4Stack[0]++; gThread4Stack[THREAD4_STACK - 1]++; - assert(gThread4Stack[0] == gThread4Stack[THREAD4_STACK - 1], "Thread 4 stack overflow.") + assertf(gThread4Stack[0] == gThread4Stack[THREAD4_STACK - 1], "Thread 4 stack overflow."); gThread5Stack[0]++; gThread5Stack[THREAD5_STACK - 1]++; - assert(gThread5Stack[0] == gThread5Stack[THREAD5_STACK - 1], "Thread 5 stack overflow.") + assertf(gThread5Stack[0] == gThread5Stack[THREAD5_STACK - 1], "Thread 5 stack overflow."); #if ENABLE_RUMBLE gThread6Stack[0]++; gThread6Stack[THREAD6_STACK - 1]++; - assert(gThread6Stack[0] == gThread6Stack[THREAD6_STACK - 1], "Thread 6 stack overflow.") + assertf(gThread6Stack[0] == gThread6Stack[THREAD6_STACK - 1], "Thread 6 stack overflow."); #endif } #endif -extern void crash_screen_init(void); extern OSViMode VI; void thread3_main(UNUSED void *arg) { setup_mesg_queues(); diff --git a/src/debugger/assert.h b/src/debugger/assert.h new file mode 100644 index 0000000000..a1019cee82 --- /dev/null +++ b/src/debugger/assert.h @@ -0,0 +1,68 @@ +#pragma once + +#define ASSERT_MESGBUF_SIZE 512 + + +#ifndef __ASSEMBLER__ + +extern char *__n64Assert_Filename; +extern u32 __n64Assert_LineNum; +extern char *__n64Assert_Condition; +extern char __n64Assert_MessageBuf[ASSERT_MESGBUF_SIZE + 1]; +extern void __n64Assert(char *fileName, u32 lineNum, char *cond); + +/** + * Will always halt with your message of choice + */ +#define errorf(message, ...) { \ + sprintf(__n64Assert_MessageBuf, message ## __VA_ARGS__); \ + __n64Assert(__FILE__, __LINE__, " errorf() "); \ +} +#define error(message) { \ + sprintf(__n64Assert_MessageBuf, message); \ + __n64Assert(__FILE__, __LINE__, " error() "); \ +} + +/** + * Wrapper for assert/aggress + */ +#define __assert_wrapper(cond) __n64Assert(__FILE__, __LINE__, (cond)) + +/** + * `aggress` and `aggressf` will always halt if `cond` is not true (handle with care) + */ +#define aggressf(cond, ...) do {\ + if ((cond) == FALSE) { \ + sprintf(__n64Assert_MessageBuf, __VA_ARGS__); \ + __assert_wrapper(#cond); \ + } \ +} while (0); +#define aggress(cond) do {\ + if ((cond) == FALSE) { \ + __n64Assert_MessageBuf[0] = 0; \ + __assert_wrapper(#cond); \ + } \ +} while (0); + +/** + * Asserts will halt the game if `cond` is not true, and DEBUG is defined. + */ +#ifdef DEBUG_ASSERTIONS +#define assert(cond) do {\ + if ((cond) == FALSE) { \ + __n64Assert_MessageBuf[0] = 0; \ + __assert_wrapper(#cond); \ + } \ +} while (0); +#define assertf(cond, ...) do {\ + if ((cond) == FALSE) { \ + sprintf(__n64Assert_MessageBuf, __VA_ARGS__); \ + __assert_wrapper(#cond); \ + } \ +} while (0); +#else // DEBUG_ASSERTIONS +#define assert(cond) +#define assertf(cond, ...) +#endif // DEBUG_ASSERTIONS + +#endif // ASSEMBLER diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c new file mode 100644 index 0000000000..5f4b98fa9b --- /dev/null +++ b/src/debugger/crash_screen.c @@ -0,0 +1,825 @@ +#include +#include +#include +#include +#include +#include "config/config_debug.h" +#include "buffers/framebuffers.h" +#include "types.h" +#include "game/puppyprint.h" +#include "audio/external.h" +#include "farcall.h" +#include "game/game_init.h" +#include "game/main.h" +#include "game/debug.h" +#include "game/rumble_init.h" +#include "game/printf.h" + +#include "crash_screen.h" +#include "map_parser.h" +#include "disasm.h" +#include "assert.h" +#include "stacktrace.h" + +#include "sm64.h" + +extern u16 sRenderedFramebuffer; +extern struct SequenceQueueItem sBackgroundMusicQueue[6]; +extern void audio_signal_game_loop_tick(void); +extern void stop_sounds_in_continuous_banks(void); +extern void read_controller_inputs(s32 threadID); +extern char *strstr(char *, char *); + +static char *crashPageNames[] = { + [CRASH_SCREEN_PAGE_SIMPLE] = "(Overview)", + [CRASH_SCREEN_PAGE_CONTEXT] = "(Context)", + [CRASH_SCREEN_PAGE_STACKTRACE] = "(Stack Trace)", +#ifdef PUPPYPRINT_DEBUG + [CRASH_SCREEN_PAGE_LOG] = "(Log)", +#endif + [CRASH_SCREEN_PAGE_DISASM] = "(Disassembly)", + [CRASH_SCREEN_PAGE_ASSERTS] = "(Assert)", +}; + +static u8 sCrashScreenCharToGlyph[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, +}; + +static u32 sCrashScreenFont[CRASH_SCREEN_GLYPH_HEIGHT * CRASH_SCREEN_FONT_ROWS * 2 + 1] = { + #include "textures/crash_custom/crash_screen_font.ia1.inc.c" +}; + +static u8 crashPage = 0; +static u8 updateBuffer = TRUE; + +static char crashScreenBuf[0x200]; + +/** + * Verbose descriptions for exception causes. + */ +char *gCauseDesc[18] = { + [EXC_INT >> 2] = "Interrupt", + [EXC_MOD >> 2] = "TLB modification", + [EXC_RMISS >> 2] = "TLB exception on load", + [EXC_WMISS >> 2] = "TLB exception on store", + [EXC_RADE >> 2] = "Address error on load", + [EXC_WADE >> 2] = "Address error on store", + [EXC_IBE >> 2] = "Bus error on inst.", + [EXC_DBE >> 2] = "Bus error on data", + [EXC_SYSCALL >> 2] = "Failed Assert: See Assert Page", + [EXC_BREAK >> 2] = "Breakpoint exception", + [EXC_II >> 2] = "Reserved instruction", + [EXC_CPU >> 2] = "Coprocessor unusable", + [EXC_OV >> 2] = "Arithmetic overflow", + [EXC_TRAP >> 2] = "Trap exception", + [EXC_VCEI >> 2] = "Virtual coherency on inst.", + [EXC_FPE >> 2] = "Floating point exception", + // These two exceptions are not enumerated like the ones above. + // They take values 23 and 31, respectively, so here they're just placed + // after the ones that do enumerate nicely. + /*EXC_WATCH */ "Watchpoint exception", + /*EXC_VCED */ "Virtual coherency on data", +}; + +char *gFpcsrDesc[6] = { + "Unimplemented operation", "Invalid operation", "Division by zero", "Overflow", "Underflow", + "Inexact operation", +}; + +static u32 sProgramPosition = 0; +static u32 sCrashScreenStackTraceCount = 0; + +static u16 gCrashScreenTextColor = 0xFFFF; +static u32 sCrashScreenPrintRow_Pixels = 0; +static u32 sCrashScreenPrintLnHeight_Pixels = CRASH_SCREEN_GLYPH_HEIGHT; +static struct { + OSThread thread; + u64 stack[THREAD2_STACK / sizeof(u64)]; + OSMesgQueue mesgQueue; + OSMesg mesg; + u16 *framebuffer; + u16 width; + u16 height; +} gCrashScreen; + + + +/** + * Splits a path string by the containing folder and the name of the file itself. + */ +char *crash_screen_ellide_string(char *str, u32 truncateLength) { + u32 string_length = strlen(str); + + if (truncateLength >= string_length) { + return str; + } + + str[string_length - truncateLength - 4] = '('; + str[string_length - truncateLength - 3] = '.'; + str[string_length - truncateLength - 2] = '.'; + str[string_length - truncateLength - 1] = '.'; + str[string_length - truncateLength - 0] = ')'; + return &str[string_length - truncateLength - 4]; +} + +static void set_text_color(u32 r, u32 g, u32 b) { + gCrashScreenTextColor = GPACK_RGBA5551(r, g, b, 255); +} + +static void reset_text_color(void) { + gCrashScreenTextColor = 0xFFFF; +} + +/** + * Sets the screen row, in pixels, at which the next print + * will be placed on the Y axis. + */ +static void crash_screen_set_print_top(u32 row) { + sCrashScreenPrintRow_Pixels = row; +} + +static void crash_screen_set_println_height(u32 height_pixels) { + sCrashScreenPrintLnHeight_Pixels = height_pixels; +} + +static void crash_screen_reset_println_height(void) { + sCrashScreenPrintLnHeight_Pixels = CRASH_SCREEN_GLYPH_HEIGHT; +} + +void crash_screen_draw_rect(s32 x, s32 y, s32 w, s32 h) { + u16 *ptr; + s32 i, j; + + ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + /** + * Instead of setting the framebuffer pixels fully dark, + * SM64 "darkens" the RGBA5551 pixel. This is done by + * shifting every RGB component right by 2 in one operation, + * essentially setting the brightness to 1/4. + */ + + // 0xe738 = 0b1110011100111000 + *ptr = ((*ptr & 0xe738) >> 2) | 1; + ptr++; + } + ptr += gCrashScreen.width - w; + } +} + +void crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { + const u32 *data; + u16 *ptr; + u32 bit; + u32 rowMask; + s32 i, j; + u32 startOfGlyphData = 0; + + if (glyph > 0x7F) return; + + startOfGlyphData = (glyph & 0xF) * CRASH_SCREEN_GLYPH_HEIGHT * 2; + if (glyph >= 64) { + startOfGlyphData++; + } + + data = &sCrashScreenFont[startOfGlyphData]; + + ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; + + u16 color = gCrashScreenTextColor; + + for (i = 0; i < CRASH_SCREEN_GLYPH_HEIGHT; i++) { + bit = 0x80000000U >> ((glyph >> 4) * CRASH_SCREEN_GLYPH_WIDTH); + rowMask = *data++; + data++; + + for (j = 0; j < (CRASH_SCREEN_GLYPH_WIDTH); j++) { + if (bit & rowMask) { + *ptr = color; + } + ptr++; + bit >>= 1; + } + ptr += gCrashScreen.width - (CRASH_SCREEN_GLYPH_WIDTH); + } + osWritebackDCacheAll(); +} + +static char *write_to_buf(char *buffer, const char *data, size_t size) { + return (char *) memcpy(buffer, data, size) + size; +} + +/** + * crash_screen_print, but automatically places a newline + * if text is xNewline units away from the right edge of the screen. + * Also respects newline characters in the evaluated string. + * + * Returns the number of newlines that were in the string, or created. + */ +int crash_screen_print_with_newlines(s32 x, s32 y, const s32 xNewline, const char *fmt, ...) { + char *ptr; + u32 glyph; + s32 size; + s32 xOffset = x; + int numNewlines = 0; + + va_list args; + va_start(args, fmt); + + size = _Printf(write_to_buf, crashScreenBuf, fmt, args); + + if (size > 0) { + ptr = crashScreenBuf; + + while (*ptr && size-- > 0) { + if (xOffset >= SCREEN_WIDTH - (xNewline + CRASH_SCREEN_X_KERNING)) { + y += 10; + xOffset = xNewline; + numNewlines += 1; + } + + glyph = sCrashScreenCharToGlyph[*ptr & 0x7f]; + + if (*ptr == '\n') { + y += 10; + xOffset = x; + ptr++; + numNewlines += 1; + continue; + } else if (*ptr == '\t') { + osSyncPrintf("TAB %d ->", xOffset); + xOffset += CRASH_SCREEN_GLYPH_WIDTH * (CRASH_SCREEN_TAB_WIDTH_CHARS - 1); + osSyncPrintf("%d\n", xOffset); + + ptr++; + continue; + } else if (glyph != 0xff) { + crash_screen_draw_glyph(xOffset, y, glyph); + } + + ptr++; + xOffset += CRASH_SCREEN_X_KERNING; + } + } + + va_end(args); + + return numNewlines; +} + +void crash_screen_print(s32 x, s32 y, const char *fmt, ...) { + char *ptr; + u32 glyph; + s32 size; + + va_list args; + va_start(args, fmt); + + size = _Printf(write_to_buf, crashScreenBuf, fmt, args); + + if (size > 0) { + ptr = crashScreenBuf; + + while (*ptr && size-- > 0) { + if (*ptr == '\t') { + ptr++; + x += CRASH_SCREEN_GLYPH_WIDTH * (CRASH_SCREEN_TAB_WIDTH_CHARS - 1); + continue; + } + glyph = sCrashScreenCharToGlyph[*ptr & 0x7f]; + + if (glyph != 0xff) { + crash_screen_draw_glyph(x, y, glyph); + } + + ptr++; + x += CRASH_SCREEN_X_KERNING; + } + } + + va_end(args); +} + +void crash_screen_println(const char *fmt, ...) { + char *ptr; + u32 glyph; + s32 size; + s32 x = CRASH_SCREEN_LEFT_MARGIN; + s32 y = sCrashScreenPrintRow_Pixels; + + va_list args; + va_start(args, fmt); + + size = _Printf(write_to_buf, crashScreenBuf, fmt, args); + + if (size > 0) { + ptr = crashScreenBuf; + + while (*ptr && size-- > 0) { + if (*ptr == '\t') { + ptr++; + x += CRASH_SCREEN_GLYPH_WIDTH * (CRASH_SCREEN_TAB_WIDTH_CHARS - 1); + continue; + } + + glyph = sCrashScreenCharToGlyph[*ptr & 0x7f]; + + if (glyph != 0xff) { + crash_screen_draw_glyph(x, y, glyph); + } + + ptr++; + x += CRASH_SCREEN_X_KERNING; + } + } + + va_end(args); + sCrashScreenPrintRow_Pixels += sCrashScreenPrintLnHeight_Pixels; +} + +void crash_screen_sleep(s32 ms) { + u64 cycles = ms * 1000LL * osClockRate / 1000000ULL; + osSetTime(0); + while (osGetTime() < cycles) { } +} + +void crash_screen_print_float_reg(s32 x, s32 y, s32 regNum, void *addr) { + u32 bits = *(u32 *) addr; + s32 exponent = ((bits & 0x7f800000U) >> 0x17) - 0x7F; + + if ((exponent >= -0x7E && exponent <= 0x7F) || bits == 0x0) { + crash_screen_print(x, y, "F%02d:%.3e", regNum, *(f32 *) addr); + } else { + crash_screen_print(x, y, "F%02d:%08XD", regNum, *(u32 *) addr); + } +} + +void crash_screen_print_fpcsr(u32 fpcsr) { + s32 i; + u32 bit = BIT(17); + + crash_screen_print(CRASH_SCREEN_LEFT_MARGIN + 90, 220, "FPCSR:%08XH", fpcsr); + for (i = 0; i < 6; i++) { + if (fpcsr & bit) { + crash_screen_print(222, 220, "(%s)", gFpcsrDesc[i]); + return; + } + bit >>= 1; + } +} + +void draw_crash_overview(OSThread *thread, s32 cause) { + __OSThreadContext *tc = &thread->context; + + int numNewlines = 0; + + crash_screen_draw_rect(0, CRASH_SCREEN_RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); + + set_text_color(0xFF, 0, 0); + crash_screen_println("Game crashed!"); + reset_text_color(); + + set_text_color(241, 196, 15); + crash_screen_println("Thread:"); + crash_screen_println("Cause:"); + reset_text_color(); + sCrashScreenPrintRow_Pixels -= (2 * CRASH_SCREEN_GLYPH_HEIGHT); + crash_screen_println(" %d", thread->id); + crash_screen_println(" %s", gCauseDesc[cause]); + +#ifdef DEBUG_EXPORT_SYMBOLS + symtable_info_t info = get_symbol_info(tc->pc); + + if (info.line != -1) { + char file_line[CRASH_SCREEN_MAX_PATH]; + + set_text_color(241, 196, 15); +#ifdef DEBUG_EXPORT_ALL_LINES + sprintf(file_line, " %s:%d", info.file, info.line); +#else // DEBUG_EXPORT_ALL_LINES + sprintf(file_line, " %s", info.file); +#endif // DEBUG_EXPORT_ALL_LINES + sCrashScreenPrintRow_Pixels += 5; + crash_screen_println("File: "); + reset_text_color(); + + numNewlines = crash_screen_print_with_newlines( + CRASH_SCREEN_LEFT_MARGIN, + sCrashScreenPrintRow_Pixels - CRASH_SCREEN_GLYPH_HEIGHT, + CRASH_SCREEN_LEFT_MARGIN, + file_line + ); + sCrashScreenPrintRow_Pixels += (numNewlines * CRASH_SCREEN_GLYPH_HEIGHT); + } +#endif // DEBUG_EXPORT_SYMBOLS + +#if defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) + if (stackTraceGenerated) { + set_text_color(241, 196, 15); + crash_screen_println("Stack Trace: "); + reset_text_color(); + + // print current func + crash_screen_println("%08X: %s", tc->pc, info.func == NULL ? "Unknown" : info.func); + // print last func + u32 ret_addr = tc->ra; + symtable_info_t ra_info = get_symbol_info(ret_addr); + crash_screen_println("%08X: %s:%d", ret_addr, ra_info.func == NULL ? "Unknown" : ra_info.func, ra_info.line); + // print up to 3 more + for (u32 i = 0; i < MIN(3, sCrashScreenStackTraceCount); i++) { + crash_screen_println(get_stack_entry(i)); + } + } +#else // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) + crash_screen_println("Address: 0x%08X", tc->pc); +#endif // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) +} + +void draw_crash_context(OSThread *thread, s32 cause) { + __OSThreadContext *tc = &thread->context; + crash_screen_draw_rect(0, CRASH_SCREEN_RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); + + crash_screen_println("Thread:%d (%s)", thread->id, gCauseDesc[cause]); + crash_screen_set_println_height(10); + crash_screen_println("PC:%08XH SR:%08XH VA:%08XH", tc->pc, tc->sr, tc->badvaddr); + osWritebackDCacheAll(); +#ifdef DEBUG_EXPORT_SYMBOLS + char *fname = parse_map(tc->pc, TRUE); + crash_screen_println("Crash at: %s", fname == NULL ? "Unknown" : fname); +#endif // DEBUG_EXPORT_SYMBOLS + crash_screen_println("AT:%08XH V0:%08XH V1:%08XH", (u32) tc->at, (u32) tc->v0, (u32) tc->v1); + crash_screen_println("A0:%08XH A1:%08XH A2:%08XH", (u32) tc->a0, (u32) tc->a1, (u32) tc->a2); + crash_screen_println("A3:%08XH T0:%08XH T1:%08XH", (u32) tc->a3, (u32) tc->t0, (u32) tc->t1); + crash_screen_println("T2:%08XH T3:%08XH T4:%08XH", (u32) tc->t2, (u32) tc->t3, (u32) tc->t4); + crash_screen_println("T5:%08XH T6:%08XH T7:%08XH", (u32) tc->t5, (u32) tc->t6, (u32) tc->t7); + crash_screen_println("S0:%08XH S1:%08XH S2:%08XH", (u32) tc->s0, (u32) tc->s1, (u32) tc->s2); + crash_screen_println("S3:%08XH S4:%08XH S5:%08XH", (u32) tc->s3, (u32) tc->s4, (u32) tc->s5); + crash_screen_println("S6:%08XH S7:%08XH T8:%08XH", (u32) tc->s6, (u32) tc->s7, (u32) tc->t8); + crash_screen_println("T9:%08XH GP:%08XH SP:%08XH", (u32) tc->t9, (u32) tc->gp, (u32) tc->sp); + crash_screen_println("S8:%08XH RA:%08XH", (u32) tc->s8, (u32) tc->ra); +#ifdef DEBUG_EXPORT_SYMBOLS + fname = parse_map(tc->ra, TRUE); + crash_screen_println("RA at: %s", fname == NULL ? "Unknown" : fname); +#endif // DEBUG_EXPORT_SYMBOLS + + crash_screen_print_fpcsr(tc->fpcsr); + + osWritebackDCacheAll(); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 0, 170, 0, &tc->fp0.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 90, 170, 2, &tc->fp2.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 180, 170, 4, &tc->fp4.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 0, 180, 6, &tc->fp6.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 90, 180, 8, &tc->fp8.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 180, 180, 10, &tc->fp10.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 0, 190, 12, &tc->fp12.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 90, 190, 14, &tc->fp14.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 180, 190, 16, &tc->fp16.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 0, 200, 18, &tc->fp18.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 90, 200, 20, &tc->fp20.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 180, 200, 22, &tc->fp22.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 0, 210, 24, &tc->fp24.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 90, 210, 26, &tc->fp26.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 180, 210, 28, &tc->fp28.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 0, 220, 30, &tc->fp30.f.f_even); + + crash_screen_reset_println_height(); +} + + +#ifdef PUPPYPRINT_DEBUG +void draw_crash_log(void) { + s32 i; + crash_screen_draw_rect(0, CRASH_SCREEN_RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); + osWritebackDCacheAll(); + for (i = 0; i < LOG_BUFFER_SIZE; i++) { + crash_screen_println(consoleLogTable[LOG_BUFFER_SIZE - i]); + } +} +#endif + +void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { + __OSThreadContext *tc = &thread->context; + + crash_screen_draw_rect(0, CRASH_SCREEN_RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); + crash_screen_println("Stack Trace from %08X:", (u32) tc->sp); + +#if defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) + // Current Func (EPC) + crash_screen_println("%08X (%s)", tc->pc, parse_map(tc->pc, TRUE)); + + // Previous Func (RA) + u32 ra = tc->ra; + symtable_info_t info = get_symbol_info(ra); + + crash_screen_println("%08X (%s:%d)", ra, info.func, info.line); + + osWritebackDCacheAll(); + + for (u32 i = 0; i < sCrashScreenStackTraceCount; i++) { + crash_screen_println(get_stack_entry(i)); + } +#else // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) + // simple stack trace + u32 sp = tc->sp; + + for (int i = 0; i < STACK_LINE_COUNT; i++) { + crash_screen_print(CRASH_SCREEN_LEFT_MARGIN, 55 + (i * 10), "%3d: %08X", i, *((u32*)(sp + (i * 4)))); + crash_screen_print(120, 55 + (i * 10), "%3d: %08X", i + STACK_LINE_COUNT, *((u32*)(sp + ((i + STACK_LINE_COUNT) * 4)))); + } +#endif // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) +} + +void draw_disasm(OSThread *thread) { + __OSThreadContext *tc = &thread->context; + + crash_screen_draw_rect(0, CRASH_SCREEN_RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); + if (sProgramPosition == 0) { + sProgramPosition = (tc->pc - 36); + } + crash_screen_println("Program Counter: %08X", sProgramPosition); + osWritebackDCacheAll(); + + int skiplines = 0; + + for (int i = 0; i < 19; i++) { + u32 addr = (sProgramPosition + (i * 4)); + + char *disasm = insn_disasm((InsnData *)addr); + + + if (disasm[0] == 0) { + crash_screen_println(" %08X", addr); + } else { +#ifdef DEBUG_EXPORT_SYMBOLS + symtable_info_t info = get_symbol_info(addr); + + if (info.func_offset == 0 && info.distance == 0) { + set_text_color(239, 196, 15); + crash_screen_println("<%s:>", info.func); + reset_text_color(); + skiplines++; + } +#ifndef DEBUG_EXPORT_ALL_LINES + // catch `jal` and `jalr` callsites + if (disasm[0] == 'j' && disasm[1] == 'a') { +#endif // DEBUG_EXPORT_ALL_LINES + if (info.line != -1) { + set_text_color(200, 200, 200); + crash_screen_println("%d:", info.line); + + reset_text_color(); + sCrashScreenPrintRow_Pixels -= sCrashScreenPrintLnHeight_Pixels; + crash_screen_println(" %s", disasm); + } +#ifndef DEBUG_EXPORT_ALL_LINES + } +#endif // DEBUG_EXPORT_ALL_LINES +#endif // DEBUG_EXPORT_SYMBOLS + else { + if (addr == tc->pc) { + set_text_color(255, 0, 0); + } else { + reset_text_color(); + } + crash_screen_println(" %s", disasm); + } + } + + } + + reset_text_color(); + osWritebackDCacheAll(); +} + +void draw_assert(OSThread *thread) { + __OSThreadContext *tc = &thread->context; + crash_screen_draw_rect(0, CRASH_SCREEN_RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); + crash_screen_set_print_top(35); + + set_text_color(0xFF, 0, 0); + crash_screen_println("Assert Failed!"); + reset_text_color(); + + + if (__n64Assert_Filename != NULL) { + // print this on the same line as `File: ` but to its right + char file_line[CRASH_SCREEN_MAX_PATH]; + sprintf(file_line, " %s:%d", __n64Assert_Filename, __n64Assert_LineNum); + set_text_color(241, 196, 15); + crash_screen_println("File/Line:"); + reset_text_color(); + int numNewlines = crash_screen_print_with_newlines( + CRASH_SCREEN_LEFT_MARGIN, + sCrashScreenPrintRow_Pixels - CRASH_SCREEN_GLYPH_HEIGHT, + CRASH_SCREEN_LEFT_MARGIN, + file_line + ); + + sCrashScreenPrintRow_Pixels += (numNewlines * CRASH_SCREEN_GLYPH_HEIGHT); + + // Print the assert condition that failed. + set_text_color(241, 196, 15); + crash_screen_println("Condition:"); + reset_text_color(); + numNewlines = crash_screen_print_with_newlines( + CRASH_SCREEN_LEFT_MARGIN, + sCrashScreenPrintRow_Pixels - CRASH_SCREEN_GLYPH_HEIGHT, + CRASH_SCREEN_LEFT_MARGIN, + " (%s)", + __n64Assert_Condition + ); + + sCrashScreenPrintRow_Pixels += (numNewlines * CRASH_SCREEN_GLYPH_HEIGHT); + + // Print the message, if assertf/aggressf/errorf were used. + if (__n64Assert_MessageBuf[0] != 0) { + set_text_color(241, 196, 15); + crash_screen_println("Message:"); + reset_text_color(); + numNewlines = + crash_screen_print_with_newlines( + CRASH_SCREEN_LEFT_MARGIN, + sCrashScreenPrintRow_Pixels - CRASH_SCREEN_GLYPH_HEIGHT, + CRASH_SCREEN_LEFT_MARGIN, + " %s", + __n64Assert_MessageBuf + ); + } + sCrashScreenPrintRow_Pixels += (numNewlines * CRASH_SCREEN_GLYPH_HEIGHT); +#if defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) + if (stackTraceGenerated) { + set_text_color(241, 196, 15); + crash_screen_println("Stack Trace:"); + reset_text_color(); + + // Print last func (we know the current func is __n64Assert) + u32 ret_addr = tc->ra; + symtable_info_t ra_info = get_symbol_info(ret_addr); + crash_screen_println("%08X: %s:%d", ret_addr, ra_info.func == NULL ? "Unknown" : ra_info.func, ra_info.line); + // Print up to 3 more + for (u32 i = 0; i < MIN(3, sCrashScreenStackTraceCount); i++) { + crash_screen_println(get_stack_entry(i)); + } + } +#else // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) + // Print address of last func (we know the current func is __n64Assert) + u32 ret_addr = tc->ra; + set_text_color(241, 196, 15); + crash_screen_println("Called From:"); + reset_text_color(); + crash_screen_println("\t0x%08X", ret_addr); +#endif // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) + } else { + crash_screen_println("No failed assert to report."); + } + + osWritebackDCacheAll(); +} + +void draw_crash_screen(OSThread *thread) { + __OSThreadContext *tc = &thread->context; + + s32 cause = ((tc->cause >> 2) & 0x1F); + if (cause == 23) { // EXC_WATCH + cause = 16; + } + if (cause == 31) { // EXC_VCED + cause = 17; + } + + if (gPlayer1Controller->buttonPressed & R_TRIG) { + crashPage++; + if (crashPage == CRASH_SCREEN_PAGE_ASSERTS && tc->cause != EXC_SYSCALL) crashPage++; + updateBuffer = TRUE; + } + if (gPlayer1Controller->buttonPressed & (L_TRIG | Z_TRIG)) { + crashPage--; + if (crashPage == CRASH_SCREEN_PAGE_ASSERTS && tc->cause != EXC_SYSCALL) crashPage--; + updateBuffer = TRUE; + } + + if (crashPage == CRASH_SCREEN_PAGE_DISASM) { + u32 sNewProgramPosition = sProgramPosition; + if (gPlayer1Controller->buttonDown & D_CBUTTONS) { + sNewProgramPosition += 4; + } + if (gPlayer1Controller->buttonDown & U_CBUTTONS) { + sNewProgramPosition -= 4; + } + + if (is_text_addr(sNewProgramPosition) && (sNewProgramPosition != sProgramPosition)) { + // Hold B to speed up scrolling + if (!(gPlayer1Controller->buttonDown & B_BUTTON)) { + crash_screen_sleep(30); + } + sProgramPosition = sNewProgramPosition; + updateBuffer = TRUE; + } + } + + if ((crashPage >= CRASH_SCREEN_PAGE_COUNT) && (crashPage != 255)) { + crashPage = 0; + } + if (crashPage == 255) { + crashPage = (CRASH_SCREEN_PAGE_COUNT - 1); + // Do not navigate to the assert page if an assert didn't happen + if (crashPage == CRASH_SCREEN_PAGE_ASSERTS && tc->cause != EXC_SYSCALL) crashPage--; + } + if (updateBuffer) { + crash_screen_draw_rect(0, 0, SCREEN_WIDTH, CRASH_SCREEN_RECT_BOUNDARY_Y); + crash_screen_set_print_top(CRASH_SCREEN_TOP_MARGIN); + crash_screen_println("Page:%02d %-19s L/Z: Left R: Right", crashPage, crashPageNames[crashPage]); + switch (crashPage) { + case CRASH_SCREEN_PAGE_SIMPLE: draw_crash_overview(thread, cause); break; + case CRASH_SCREEN_PAGE_CONTEXT: draw_crash_context(thread, cause); break; + case CRASH_SCREEN_PAGE_STACKTRACE: draw_stacktrace(thread, cause); break; +#ifdef PUPPYPRINT_DEBUG + case CRASH_SCREEN_PAGE_LOG: draw_crash_log(); break; +#endif + case CRASH_SCREEN_PAGE_DISASM: draw_disasm(thread); break; + case CRASH_SCREEN_PAGE_ASSERTS: draw_assert(thread); break; + } + + osWritebackDCacheAll(); + osViBlack(FALSE); + osViSwapBuffer(gCrashScreen.framebuffer); + updateBuffer = FALSE; + } +} + +OSThread *get_crashed_thread(void) { + OSThread *thread = __osGetCurrFaultedThread(); + + while (thread->priority != -1) { + if (thread->priority > OS_PRIORITY_IDLE && thread->priority < OS_PRIORITY_APPMAX + && ((thread->flags & (BIT(0) | BIT(1))) != 0)) { + return thread; + } + thread = thread->tlnext; + } + return NULL; +} + +void thread2_crash_screen(UNUSED void *arg) { + OSMesg mesg; + OSThread *thread = NULL; + + osSetEventMesg(OS_EVENT_CPU_BREAK, &gCrashScreen.mesgQueue, (OSMesg) 1); + osSetEventMesg(OS_EVENT_FAULT, &gCrashScreen.mesgQueue, (OSMesg) 2); + while (TRUE) { + if (thread == NULL) { + osRecvMesg(&gCrashScreen.mesgQueue, &mesg, 1); + thread = get_crashed_thread(); + gCrashScreen.framebuffer = (RGBA16 *) gFramebuffers[sRenderedFramebuffer]; + if (thread) { + gCrashScreen.thread.priority = 15; + stop_sounds_in_continuous_banks(); + stop_background_music(sBackgroundMusicQueue[0].seqId); + audio_signal_game_loop_tick(); + crash_screen_sleep(200); + play_sound(SOUND_MARIO_WAAAOOOW, gGlobalSoundSource); + audio_signal_game_loop_tick(); + crash_screen_sleep(200); + if (stackTraceGenerated == FALSE) { + sCrashScreenStackTraceCount = generate_stack(thread); + stackTraceGenerated = TRUE; + } + // If an assert happened, go straight to that page + if (thread->context.cause == EXC_SYSCALL) { + crashPage = CRASH_SCREEN_PAGE_ASSERTS; + } + continue; + } + } else { + if (gControllerBits) { +#if ENABLE_RUMBLE + block_until_rumble_pak_free(); +#endif + osContStartReadDataEx(&gSIEventMesgQueue); + } + read_controller_inputs(THREAD_2_CRASH_SCREEN); + draw_crash_screen(thread); + } + } +} + +void crash_screen_init(void) { + gCrashScreen.framebuffer = (RGBA16 *) gFramebuffers[sRenderedFramebuffer]; + gCrashScreen.width = SCREEN_WIDTH; + gCrashScreen.height = SCREEN_HEIGHT; + osCreateMesgQueue(&gCrashScreen.mesgQueue, &gCrashScreen.mesg, 1); + osCreateThread(&gCrashScreen.thread, THREAD_2_CRASH_SCREEN, thread2_crash_screen, NULL, + (u8 *) gCrashScreen.stack + sizeof(gCrashScreen.stack), + OS_PRIORITY_APPMAX + ); + osStartThread(&gCrashScreen.thread); +} + diff --git a/src/debugger/crash_screen.h b/src/debugger/crash_screen.h new file mode 100644 index 0000000000..76fef00411 --- /dev/null +++ b/src/debugger/crash_screen.h @@ -0,0 +1,41 @@ +#ifndef CRASH_SCREEN_H +#define CRASH_SCREEN_H + +#define CRASH_SCREEN_MAX_PATH 256 + +// Configurable Defines +#define CRASH_SCREEN_X_KERNING 6 +#define CRASH_SCREEN_GLYPH_WIDTH 8 +#define CRASH_SCREEN_GLYPH_HEIGHT 12 +#define CRASH_SCREEN_FONT_ROWS 16 + +// Margins for crash screen prints +#define CRASH_SCREEN_LEFT_MARGIN 16 +#define CRASH_SCREEN_TOP_MARGIN 16 + +// Tab width ('\t') for crash screen prints, in characters. +// Note that we only treat tabs as a wide space. +#define CRASH_SCREEN_TAB_WIDTH_CHARS 4 + +// Where to stop drawing the Header rectangle and where to start drawing the Body rectangle. +#define CRASH_SCREEN_RECT_BOUNDARY_Y CRASH_SCREEN_TOP_MARGIN + CRASH_SCREEN_GLYPH_HEIGHT + 5 + +enum CrashPages { + CRASH_SCREEN_PAGE_SIMPLE, + CRASH_SCREEN_PAGE_CONTEXT, + CRASH_SCREEN_PAGE_STACKTRACE, +#ifdef PUPPYPRINT_DEBUG + CRASH_SCREEN_PAGE_LOG, +#endif + CRASH_SCREEN_PAGE_DISASM, + CRASH_SCREEN_PAGE_ASSERTS, + CRASH_SCREEN_PAGE_COUNT +}; + +// Exports for the debugger/ system +char *crash_screen_ellide_string(char *str, u32 truncateLength); + +// Exports for the rest of the game +void crash_screen_init(void); + +#endif // CRASH_SCREEN_H diff --git a/src/debugger/disasm.c b/src/debugger/disasm.c new file mode 100644 index 0000000000..eb588bd04d --- /dev/null +++ b/src/debugger/disasm.c @@ -0,0 +1,397 @@ +#include +#include + +#include "sm64.h" +#include "macros.h" +#include "farcall.h" + +#include "crash_screen.h" +#include "disasm.h" +#include "map_parser.h" + +static char insn_as_string[100]; + +InsnTemplate insn_db[] = { + // We want instructions with opcodes first (prioritized) + + // load/store + {I_TYPE, PARAM_LUI, 0b001111, 0, "lui"}, + {I_TYPE, PARAM_NONE, 0b100000, 0, "lb"}, + {I_TYPE, PARAM_NONE, 0b100100, 0, "lbu"}, + {I_TYPE, PARAM_NONE, 0b101000, 0, "sb"}, + {I_TYPE, PARAM_NONE, 0b100001, 0, "lh"}, + {I_TYPE, PARAM_NONE, 0b100101, 0, "lhu"}, + {I_TYPE, PARAM_NONE, 0b101001, 0, "sh"}, + {I_TYPE, PARAM_NONE, 0b100011, 0, "lw"}, + {I_TYPE, PARAM_NONE, 0b101011, 0, "sw"}, + {I_TYPE, PARAM_NONE, 0b110111, 0, "ld"}, + {I_TYPE, PARAM_NONE, 0b111111, 0, "sd"}, + {I_TYPE, PARAM_FLOAT_RT, 0b110001, 0, "lwc1"}, + {I_TYPE, PARAM_FLOAT_RT, 0b111001, 0, "swc1"}, + {I_TYPE, PARAM_FLOAT_RT, 0b110101, 0, "ldc1"}, + {I_TYPE, PARAM_FLOAT_RT, 0b111101, 0, "sdc1"}, + + // unaligned + {I_TYPE, PARAM_NONE, 0b100010, 0, "lwl"}, + {I_TYPE, PARAM_NONE, 0b100110, 0, "lwr"}, + {I_TYPE, PARAM_NONE, 0b101010, 0, "swl"}, + {I_TYPE, PARAM_NONE, 0b101110, 0, "swr"}, + // atomics + {I_TYPE, PARAM_NONE, 0b110000, 0, "ll"}, + {I_TYPE, PARAM_NONE, 0b111000, 0, "sc"}, + {I_TYPE, PARAM_NONE, 0b111100, 0, "scd"}, + // branches + {I_TYPE, PARAM_SWAP_RS_IMM, 0b000100, 0, "beq"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b010100, 0, "beql"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b000101, 0, "bne"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b010101, 0, "bnel"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b000111, 0, "bgtz"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b010111, 0, "bgtzl"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b000110, 0, "blez"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b010110, 0, "blezl"}, + {I_TYPE, PARAM_NONE, 0b001010, 0, "slti"}, + {I_TYPE, PARAM_NONE, 0b001011, 0, "sltiu"}, + + // jal (special) + {J_TYPE, PARAM_JAL, 0b000011, 0, "jal"}, + {J_TYPE, PARAM_JUMP, 0b000010, 0, "j"}, + + // bitwise ops (which are opcodes) + {I_TYPE, PARAM_NONE, 0b001100, 0, "andi"}, + {I_TYPE, PARAM_NONE, 0b001101, 0, "ori"}, + {I_TYPE, PARAM_NONE, 0b001110, 0, "xori"}, + + + // arithmetic + {I_TYPE, PARAM_SWAP_RS_IMM, 0b011000, 0, "daddi"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b011001, 0, "daddiu"}, + // and now the ones with 0 for the opcode + {R_TYPE, PARAM_NONE, 0, 0b100000, "add"}, + {R_TYPE, PARAM_NONE, 0, 0b100001, "addu"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b001000, 0, "addi"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b001001, 0, "addiu"}, + {R_TYPE, PARAM_NONE, 0, 0b100010, "sub"}, + {R_TYPE, PARAM_NONE, 0, 0b100011, "subu"}, + {R_TYPE, PARAM_NONE, 0, 0b011000, "mult"}, + {R_TYPE, PARAM_NONE, 0, 0b011001, "multu"}, + {R_TYPE, PARAM_NONE, 0, 0b011010, "div"}, + {R_TYPE, PARAM_NONE, 0, 0b011011, "divu"}, + {R_TYPE, PARAM_MULT_MOVE, 0, 0b010000, "mfhi"}, + {R_TYPE, PARAM_MULT_MOVE, 0, 0b010001, "mthi"}, + {R_TYPE, PARAM_MULT_MOVE, 0, 0b010010, "mflo"}, + {R_TYPE, PARAM_MULT_MOVE, 0, 0b010011, "mtlo"}, + {R_TYPE, PARAM_NONE, 0, 0b101010, "slt"}, + {R_TYPE, PARAM_NONE, 0, 0b101011, "sltu"}, + + // bitwise ops (which are functions) + {R_TYPE, PARAM_NONE, 0, 0b100100, "and"}, + {R_TYPE, PARAM_NONE, 0, 0b100101, "or"}, + {R_TYPE, PARAM_NONE, 0, 0b100110, "xor"}, + {R_TYPE, PARAM_BITSHIFT, 0, 0b000000, "sll"}, + {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b000100, "sllv"}, + {R_TYPE, PARAM_BITSHIFT, 0, 0b000010, "srl"}, + {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b000110, "srlv"}, + {R_TYPE, PARAM_BITSHIFT, 0, 0b000011, "sra"}, + {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b000111, "srav"}, + {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b100111, "nor"}, + + {R_TYPE, PARAM_NONE, 0, 0b001001, "jalr"}, + {R_TYPE, PARAM_NONE, 0, 0b001000, "jr"}, + {R_TYPE, PARAM_TRAP, 0, 0b110100, "teq"}, + {R_TYPE, PARAM_EMUX, 0, 0b110110, "tne"}, + + {0, PARAM_SYSCALL, 0, 0b001100, "syscall"}, + + // instructions involving doubles (deprioritized on the list) + {R_TYPE, PARAM_NONE, 0, 0b101101, "daddu"}, + {R_TYPE, PARAM_NONE, 0, 0b101110, "dsub"}, + {R_TYPE, PARAM_NONE, 0, 0b101111, "dsubu"}, + {R_TYPE, PARAM_NONE, 0, 0b011101, "dmultu"}, + {R_TYPE, PARAM_NONE, 0, 0b011110, "ddiv"}, + {R_TYPE, PARAM_NONE, 0, 0b011111, "ddivu"}, + {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b010100, "dsllv"}, + {R_TYPE, PARAM_BITSHIFT, 0, 0b111100, "dsll32"}, + {R_TYPE, PARAM_BITSHIFT, 0, 0b111110, "dsrl32"}, + {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b010110, "dsrlv"}, + {R_TYPE, PARAM_BITSHIFT, 0, 0b111111, "dsra32"}, + {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b010111, "dsrav"}, +}; + + +char __mips_gpr[][4] = { + "$r0", + "$at", + "$v0", "$v1", + "$a0", "$a1", "$a2", "$a3", + "$t0", "$t1", "$t2", "$t3", "$t4", "$t5", "$t6", "$t7", + "$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7", + "$t8", "$t9", + "$k0", "$k1", + "$gp", "$sp", "$fp", "$ra", + "$lo", "$hi" +}; + +char __mips_fpreg[][5] = { + "$f0", "$f1", "$f2", "$f3", + "$f4", "$f5", "$f6", "$f7", + "$f8", "$f9", "$f10", "$f11", + "$f12", "$f13", "$f14", "$f15", + "$f16", "$f17", "$f18", "$f19", + "$f20", "$f21", "$f22", "$f23", + "$f24", "$f25", "$f26", "$f27", + "$f28", "$f29", "$f30", "$f31", +}; + +u8 insn_is_jal(Insn *i) { + return (i->opcode == 0b000011); +} + +u8 insn_is_jalr(Insn *i) { + return (i->opcode == 0) && (i->rdata.function == 0b001001); +} + +// Last Resort C0/C1 disassembler, from libdragon +static void c1_disasm(u32 *ptr, char *out) { + static const char *fpu_ops[64]= { + "radd", "rsub", "rmul", "rdiv", "ssqrt", "sabs", "smov", "sneg", + "sround.l", "strunc.l", "sceil.l", "sfloor.l", "sround.w", "strunc.w", "sceil.w", "sfloor.w", + "*", "*", "*", "*", "*", "*", "*", "*", + "*", "*", "*", "*", "*", "*", "*", "*", + "scvt.s", "scvt.d", "*", "*", "scvt.w", "scvt.l", "*", "*", + "*", "*", "*", "*", "*", "*", "*", "*", + "hc.f", "hc.un", "hc.eq", "hc.ueq", "hc.olt", "hc.ult", "hc.ole", "hc.ule", + "hc.sf", "hc.ngle", "hc.seq", "hc.ngl", "hc.lt", "hc.nge", "hc.le", "hc.ngt", + }; + + char symbuf[64]; + + // Disassemble MIPS instruction + u32 pc = (u32)ptr; + u32 op = *ptr; + s16 imm16 = op & 0xFFFF; + u32 tgt16 = (pc + 4) + (imm16 << 2); + u32 imm26 = op & 0x3FFFFFF; + u32 tgt26 = ((pc + 4) & 0xf0000000) | (imm26 << 2); + const char *rs = __mips_gpr[(op >> 21) & 0x1F]; + const char *rt = __mips_gpr[(op >> 16) & 0x1F]; + const char *rd = __mips_gpr[(op >> 11) & 0x1F]; + const char *opn = "unimpl"; + if (((op >> 26) & 0x3F) == 17) { + u32 sub = (op >> 21) & 0x1F; + switch (sub) { + case 0: opn = "gmfc1"; break; + case 1: opn = "gdmfc1"; break; + case 4: opn = "gmtc1"; break; + case 5: opn = "gdmtc1"; break; + case 8: switch ((op >> 16) & 0x1F) { + case 0: opn = "ybc1f"; break; + case 2: opn = "ybc1fl"; break; + case 1: opn = "ybc1t"; break; + case 3: opn = "ybc1tl"; break; + } break; + case 16: case 17: case 20: case 21: + opn = fpu_ops[(op >> 0) & 0x3F]; + sprintf(symbuf, "%s.%s", opn, (sub == 16) ? "s" : (sub == 17) ? "d" : (sub == 20) ? "w" : "l"); + opn = symbuf; + rt = __mips_fpreg[(op >> 16) & 0x1F]; + rs = __mips_fpreg[(op >> 11) & 0x1F]; + rd = __mips_fpreg[(op >> 6) & 0x1F]; + break; + } + } + switch (*opn) { +#ifdef DEBUG_EXPORT_SYMBOLS + /* op tgt26 */ case 'j': sprintf(out, "%-9s %08x <%s>", opn+1, tgt26, parse_map(tgt26, FALSE)); break; + /* op rs, rt, tgt16 */case 'b': sprintf(out, "%-9s %s, %s, %08x <%s>", opn+1, rs, rt, tgt16, parse_map(tgt16, TRUE)); break; + /* op tgt16 */ case 'y': sprintf(out, "%-9s %08x <%s>", opn+1, tgt16, parse_map(tgt16, TRUE)); break; +#else + /* op tgt26 */ case 'j': sprintf(out, "%-9s %08x", opn+1, tgt26); break; + /* op rs, rt, tgt16 */case 'b': sprintf(out, "%-9s %s, %s, %08x", opn+1, rs, rt, tgt16); break; + /* op tgt16 */ case 'y': sprintf(out, "%-9s %08x", opn+1, tgt16); break; +#endif // DEBUG_EXPORT_SYMBOLS + /* op rt, rs, imm */ case 'i': sprintf(out, "%-9s %s, %s, %d", opn+1, rt, rs, (s16)op); break; + /* op rt, imm */ case 'k': sprintf(out, "%-9s %s, %d", opn+1, rt, (s16)op); break; + /* op rt, imm(rs) */ case 'm': sprintf(out, "%-9s %s, %d(%s)", opn+1, rt, (s16)op, rs); break; + /* op fd, imm(rs) */ case 'n': sprintf(out, "%-9s %s, %d(%s)", opn+1, __mips_fpreg[(op >> 16) & 0x1F], (s16)op, rs); break; + /* op rd, rs, rt */ case 'r': sprintf(out, "%-9s %s, %s, %s", opn+1, rd, rs, rt); break; + /* op rd, rs */ case 's': sprintf(out, "%-9s %s, %s", opn+1, rd, rs); break; + /* op rd, rt, sa */ case 'e': sprintf(out, "%-9s %s, %s, %ld", opn+1, rd, rt, (op >> 6) & 0x1F); break; + /* op rs */ case 'w': sprintf(out, "%-9s %s", opn+1, rs); break; + /* op rd */ case 'c': sprintf(out, "%-9s %s", opn+1, rd); break; + /* op */ case 'z': sprintf(out, "%-9s", opn+1); break; + /* op fd, fs, ft */ case 'f': sprintf(out, "%-9s %s, %s, %s", opn+1, rd, rs, rt); break; + /* op rt, fs */ case 'g': sprintf(out, "%-9s %s, %s", opn+1, rt, __mips_fpreg[(op >> 11) & 0x1F]); break; + /* op rs, rt */ case 'h': sprintf(out, "%-9s %s, %s", opn+1, rs, rt); break; + /* op code20 */ case 'a': sprintf(out, "%-9s 0x%lx", opn+1, (op>>6) & 0xFFFFF); break; + /* op rs, rt, code */ case 't': sprintf(out, "%-9s %s, %s, 0x%lx", opn+1, rs, rt, (op>>6) & 0x3FF); break; + default: sprintf(out, "%-9s", opn+1); break; + } +} + +char *cop1_insn_disasm(InsnData *pc) { + c1_disasm((u32 *)pc, insn_as_string); + + return insn_as_string; +} + +char *branch_insn_disasm(InsnData insn) { + static char *insn_names[] = { + [0b00001] = "bgez", + [0b00011] = "bgezl", + [0b10001] = "bgezal", + [0b10011] = "bgezall", + [0b00000] = "bltz", + [0b00010] = "bltzl", + [0b10000] = "bltzal", + [0b10010] = "bltzall", + }; + char *strp = &insn_as_string[0]; + char *rs = __mips_gpr[insn.b.rs]; + u16 offset = insn.b.offset; + + for (int i = 0; i < ARRAY_COUNT(insn_as_string); i++) insn_as_string[i] = 0; + + sprintf(strp, "%-9s %s %04X", insn_names[insn.b.sub], rs, offset); + + return insn_as_string; +} + +char *insn_disasm(InsnData *addr) { + InsnData insn = *addr; + char *strp = &insn_as_string[0]; + int successful_print = 0; + u32 target; + + if (insn.d == 0) { // trivial case + return "nop"; + } + + if (insn.i.opcode == OP_BRANCH) { + return branch_insn_disasm(insn); + } + if (insn.i.opcode == OP_COP0) { + return "cop0 (UNIMPL)"; + } + if (insn.i.opcode == OP_COP1) { + return cop1_insn_disasm(addr); + } + + for (int i = 0; i < ARRAY_COUNT(insn_as_string); i++) insn_as_string[i] = 0; + + for (int i = 0; i < ARRAY_COUNT(insn_db); i++) { + if (insn.i.opcode != 0 && insn.i.opcode == insn_db[i].opcode) { + switch (insn_db[i].arbitraryParam) { + case PARAM_SWAP_RS_IMM: + strp += sprintf(strp, "%-9s %s %s %04X", insn_db[i].name, + __mips_gpr[insn.i.rt], + __mips_gpr[insn.i.rs], + insn.i.immediate + ); break; + case PARAM_LUI: + strp += sprintf(strp, "%-9s %s %04X", insn_db[i].name, + __mips_gpr[insn.i.rt], + insn.i.immediate + ); break; + break; + case PARAM_JAL: { + target = 0x80000000 | ((insn.d & 0x1FFFFFF) * 4); +#ifdef DEBUG_EXPORT_SYMBOLS + char symBuffer[CRASH_SCREEN_MAX_PATH]; + sprintf(symBuffer, "%s(%08X)", parse_map(target, FALSE), target); + strp += sprintf(strp, "%-9s %s", insn_db[i].name, crash_screen_ellide_string(symBuffer, 30)); +#else + strp += sprintf(strp, "%-9s %08X", insn_db[i].name, + target + ); +#endif // DEBUG_EXPORT_SYMBOLS + break; + } + case PARAM_JUMP: + target = 0x80000000 | (insn.d & 0x03FFFFFF); + strp += sprintf(strp, "%-9s %08X", insn_db[i].name, + target + ); + break; + case PARAM_FLOAT_RT: + strp += sprintf(strp, "%-9s %s, %04X (%s)", insn_db[i].name, + __mips_fpreg[insn.i.rt], + insn.i.immediate, + __mips_gpr[insn.i.rs] + ); break; + case PARAM_NONE: + strp += sprintf(strp, "%-9s %s %04X (%s)", insn_db[i].name, + __mips_gpr[insn.i.rt], + insn.i.immediate, + __mips_gpr[insn.i.rs] + ); break; + + } + successful_print = 1; + break; + } else if ( (insn.i.rdata.function == 0 && insn.i.opcode == 0) // specifically catch `sll` + || (insn.i.rdata.function != 0 && insn.i.rdata.function == insn_db[i].function) + ) { + switch (insn_db[i].arbitraryParam) { + case PARAM_BITSHIFT: + strp += sprintf(strp, "%-9s %s %s %04X", insn_db[i].name, + __mips_gpr[insn.i.rdata.rd], + __mips_gpr[insn.i.rt], + insn.i.rdata.shift_amt + ); + break; + case PARAM_SWAP_RS_RT: + strp += sprintf(strp, "%-9s %s %s %s", insn_db[i].name, + __mips_gpr[insn.i.rdata.rd], + __mips_gpr[insn.i.rt], + __mips_gpr[insn.i.rs] + ); + break; + case PARAM_MULT_MOVE: + strp += sprintf(strp, "%-9s %s", insn_db[i].name, + __mips_gpr[insn.i.rdata.rd] + ); + break; + case PARAM_EMUX: + target = (insn.d >> 6) & 0x3FF; + if (insn.i.rs == insn.i.rt) { + strp += sprintf(strp, "%-9s %s 0x%02X", "emux", + __mips_gpr[insn.i.rs], + target + ); + } else { + strp += sprintf(strp, "%-9s %s %s", insn_db[i].name, + __mips_gpr[insn.i.rs], + __mips_gpr[insn.i.rt] + ); + } + break; + case PARAM_TRAP: + strp += sprintf(strp, "%-9s %s %s", insn_db[i].name, + __mips_gpr[insn.i.rs], + __mips_gpr[insn.i.rt] + ); + break; + case PARAM_SYSCALL: + strp += sprintf(strp, "%-9s %d", insn_db[i].name, + (insn.d & 0x03FFFFC0) >> 6 + ); + break; + case PARAM_NONE: + strp += sprintf(strp, "%-9s %s %s %s", insn_db[i].name, + __mips_gpr[insn.i.rdata.rd], + __mips_gpr[insn.i.rs], + __mips_gpr[insn.i.rt] + ); + break; + + } + successful_print = 1; + break; + } + } + if (successful_print == 0) { + strp += sprintf(strp, "unimpl %08X", insn.d); + } + + return insn_as_string; +} diff --git a/src/debugger/disasm.h b/src/debugger/disasm.h new file mode 100644 index 0000000000..609ed17561 --- /dev/null +++ b/src/debugger/disasm.h @@ -0,0 +1,88 @@ +#pragma once + +enum InsnTypes { + R_TYPE, + I_TYPE, + J_TYPE, + COP0, + COP1, +}; + +enum ParamTypes { + PARAM_NONE, + PARAM_SWAP_RS_IMM, + PARAM_BITSHIFT, + PARAM_FLOAT_RT, + PARAM_SWAP_RS_RT, + PARAM_JAL, + PARAM_JUMP, + PARAM_JR, + PARAM_LUI, + PARAM_MULT_MOVE, + PARAM_TRAP, + PARAM_EMUX, + PARAM_SYSCALL, +}; + +typedef struct PACKED { + u16 rd : 5; + u16 shift_amt : 5; + u16 function : 6; +} RTypeData; + +typedef struct PACKED { + u16 opcode : 6; + u16 rs : 5; + u16 rt : 5; + union { + RTypeData rdata; + u16 immediate; + }; +} Insn; + +typedef struct PACKED { + u16 opcode : 6; + u16 fmt : 5; + u16 ft : 5; + u16 fs : 5; + u16 fd : 5; + u16 func : 6; +} CzInsn; + +typedef struct PACKED { + u16 regimm : 6; + u16 rs : 5; + u16 sub : 5; + u16 offset; +} BranchInsn; + +typedef union { + Insn i; + CzInsn f; + BranchInsn b; + u32 d; +} InsnData; + +typedef struct PACKED { + u32 type; + u32 arbitraryParam; + u16 opcode : 6; + u16 function : 6; + u8 name[10]; +} InsnTemplate; + +typedef struct PACKED { + u32 type; + u32 arbitraryParam; + u16 function : 6; + u8 name[10]; +} COPzInsnTemplate; + +#define OP_COP0 0b010000 +#define OP_COP1 0b010001 +#define OP_BRANCH 0b000001 // technically "REGIMM" + +extern char *insn_disasm(InsnData *insn); +extern u8 insn_is_jal(Insn *i); +extern u8 insn_is_jalr(Insn *i); + diff --git a/src/debugger/map_parser.c b/src/debugger/map_parser.c new file mode 100644 index 0000000000..26b72834b9 --- /dev/null +++ b/src/debugger/map_parser.c @@ -0,0 +1,83 @@ +#include +#include +#include +#include +#include +#include "game/memory.h" + +#include "map_parser.h" +#include "symtable.h" + +#ifdef DEBUG_EXPORT_SYMBOLS + +#define UNKNOWN_SYMBOL "???" + +char* __symbolize(void *vaddr, char *buf, int size, u32 andOffset) { + symtable_header_t symt = symt_open(); + if (symt.head[0]) { + u32 addr = (u32)vaddr; + int idx = 0; + addrtable_entry_t a = symt_addrtab_search(&symt, addr, &idx); + while (!ADDRENTRY_IS_FUNC(a)) { + a = symt_addrtab_entry(&symt, --idx); + } + + symtable_entry_t ALIGNED16 entry; + // Read the symbol name + symt_entry_fetch(&symt, &entry, idx); + char *func = symt_entry_func(&symt, &entry, addr, buf, size-12); + char lbuf[12]; + if (andOffset) { + sprintf(lbuf, "+0x%lx", addr - ADDRENTRY_ADDR(a)); + } else { + lbuf[0] = 0; + } + entry.func_off = addr - ADDRENTRY_ADDR(a); + + return strcat(func, lbuf); + } + sprintf(buf, "%s", UNKNOWN_SYMBOL); + return buf; +} + +char *parse_map(u32 addr, u32 andOffset) { + static char map_name[64] ALIGNED16; + char *ret = map_name; + + __symbolize((u32*)addr, map_name, sizeof(map_name), andOffset); + + if (ret[0] == ' ') { + ret++; + } + return ret; +} + +symtable_info_t get_symbol_info(u32 addr) { + static char filebuf[100]; + void *vaddr = (void *)addr; + symtable_header_t symt = symt_open(); + + if (symt.head[0]) { + symtable_info_t info; + u32 addr = (u32)vaddr; + int idx = 0; + symt_addrtab_search(&symt, addr, &idx); + + symtable_entry_t ALIGNED16 entry; + + // Read the symbol name + filebuf[0] = 0; + symt_entry_fetch(&symt, &entry, idx); + info.line = entry.line; + info.func_offset = entry.func_off; + addrtable_entry_t a = symt_addrtab_entry(&symt, idx); + info.distance = addr - ADDRENTRY_ADDR(a); + info.file = symt_entry_file(&symt, &entry, filebuf, sizeof(filebuf)); + info.func = parse_map(addr, FALSE); + + return info; + } + return (symtable_info_t){.line = -1}; +} + +#endif // DEBUG_EXPORT_SYMBOLS diff --git a/src/debugger/map_parser.h b/src/debugger/map_parser.h new file mode 100644 index 0000000000..21f02de718 --- /dev/null +++ b/src/debugger/map_parser.h @@ -0,0 +1,16 @@ +#pragma once + +#include "farcall.h" + +typedef struct { + char *file; + char *func; + int line; + u16 distance; + u16 func_offset; +} symtable_info_t; + +symtable_info_t get_symbol_info(u32 vaddr); +extern far char *parse_map(u32 pc, u32 andOffset); +extern far symtable_info_t walk_stack(u32 *addr); +extern far char* __symbolize(void *vaddr, char *buf, int size, u32 andOffset); diff --git a/src/debugger/stacktrace.c b/src/debugger/stacktrace.c new file mode 100644 index 0000000000..a35d683f6f --- /dev/null +++ b/src/debugger/stacktrace.c @@ -0,0 +1,115 @@ +#include +#include +#include + +#include "config/config_debug.h" + +#include "map_parser.h" +#include "symtable.h" +#include "stacktrace.h" +#include "disasm.h" + +#if defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) + +static StackFrame stack[STACK_LINE_COUNT]; +static u32 stackIdx; +u32 stackTraceGenerated = FALSE; + +#define STACK_END_STR "[End of stack]" + +static u8 is_top_of_stack(u32 ra) { + return (ra == ((u32)__osCleanupThread)); +} + +static void add_entry_to_stack(u32 addr, u32 ra, symtable_info_t *info) { + StackFrame *frame = &stack[stackIdx++]; + + frame->func = addr; + frame->offset = info->func_offset; + frame->ra = ra; + frame->line = info->line; + sprintf(frame->funcname, "%s", info->func); +} + +char *get_stack_entry(u32 idx) { + static char stackbuf[100]; + + sprintf(stackbuf, "%08X: %s:%d", stack[idx].func, stack[idx].funcname, stack[idx].line); + + return stackbuf; +} + +u32 generate_stack(OSThread *thread) { + static u32 breadcrumb = 0; + symtable_header_t symt = symt_open(); + + __OSThreadContext *tc = &thread->context; + + u32 sp = tc->sp; + breadcrumb = tc->ra; + + while (1) { // dont know the end goal yet + sp += 4; + + u32 val = *(u32*)sp; + + // make sure we're working on an actual address + if (is_text_addr(val + CALLSITE_OFFSET)) { + int idx = 0; + symtable_info_t info = get_symbol_info(val + CALLSITE_OFFSET); + addrtable_entry_t funcstart = symt_addrtab_search(&symt, breadcrumb, &idx); + + // If we can't logically go further, we're done! + if (is_top_of_stack(val)) { + symtable_info_t info = get_symbol_info(val + CALLSITE_OFFSET); + info.func = STACK_END_STR; + add_entry_to_stack(val + CALLSITE_OFFSET, breadcrumb, &info); + breadcrumb = val; + return stackIdx; + } + + // get the start of the current frame's func + while (!ADDRENTRY_IS_FUNC(funcstart) && !ADDRENTRY_IS_INLINE(funcstart)) { + funcstart = symt_addrtab_entry(&symt, --idx); + } + + // Make sure the address is an actual callsite + if (info.distance == 0) { + u32 jal = *(u32*)(val + CALLSITE_OFFSET); + + if (insn_is_jal((Insn *) &jal)) { + u32 jalTarget = 0x80000000 | ((jal & 0x03FFFFFF) * 4); + + // make sure JAL is to the current func + if (jalTarget == ADDRENTRY_ADDR(funcstart)) { + add_entry_to_stack(val + CALLSITE_OFFSET, breadcrumb, &info); + breadcrumb = val; + } else { + // Just in case we're on a weird boundary, find the _previous_ + // function start and see if we jal'd to there instead. + do { + funcstart = symt_addrtab_entry(&symt, --idx); + } while (!ADDRENTRY_IS_FUNC(funcstart) && !ADDRENTRY_IS_INLINE(funcstart)); + + if (jalTarget == ADDRENTRY_ADDR(funcstart)) { + add_entry_to_stack(val + CALLSITE_OFFSET, breadcrumb, &info); + breadcrumb = val; + } + } + } else if (insn_is_jalr((Insn *) &jal)) { + // Always add a JALR to the stack, in absence of a better heuristic + add_entry_to_stack(val + CALLSITE_OFFSET, breadcrumb, &info); + breadcrumb = val; + } + } + + if (stackIdx >= STACK_LINE_COUNT) { + break; + } + } + } + + return stackIdx; +} + +#endif // DEBUG_EXPORT_SYMBOLS && DEBUG_FULL_STACK_TRACE diff --git a/src/debugger/stacktrace.h b/src/debugger/stacktrace.h new file mode 100644 index 0000000000..521f627485 --- /dev/null +++ b/src/debugger/stacktrace.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include "segment_symbols.h" + +typedef struct { + u32 func; + u32 offset; + u32 ra; + int line; + char funcname[100]; +} StackFrame; + +#define STACK_TRAVERSAL_LIMIT 100 +#define STACK_LINE_COUNT 17 +// RA points to 2 instructions past any given callsite +#define CALLSITE_OFFSET -8 + +extern u32 stackTraceGenerated; + +// libultra import +extern void __osCleanupThread(); + +extern far u32 generate_stack(OSThread *); +extern far char *get_stack_entry(u32 idx); + +static u8 is_text_addr(u32 addr) { + if ((addr >= (u32)_mainSegmentStart) && (addr <= (u32)_mainSegmentTextEnd)) { + return TRUE; + } + else if ((addr >= (u32)_engineSegmentStart) && (addr <= (u32)_engineSegmentTextEnd)) { + return TRUE; + } + else if ((addr >= (u32)_goddardSegmentStart) && (addr <= (u32)_goddardSegmentTextEnd)) { + return TRUE; + } + + return FALSE; +} diff --git a/src/debugger/symtable.c b/src/debugger/symtable.c new file mode 100644 index 0000000000..5047799df5 --- /dev/null +++ b/src/debugger/symtable.c @@ -0,0 +1,190 @@ +#include +#include +#include +#include +#include +#include "segments.h" +#include "game/memory.h" + +#include "symtable.h" + +#ifdef DEBUG_EXPORT_SYMBOLS + +u32 SYMT_ROM = 0xFFFFFFFF; +extern u8 _mapDataSegmentRomStart[]; +// The start and end of the exception vector +extern u8 __osExceptionPreamble[]; +extern u8 send_mesg[]; + +#define is_in_exception(addr) ((addr) >= (u32)__osExceptionPreamble && (addr) < (u32)send_mesg) + +// code provided by Wiseguy +static void headless_dma(u32 devAddr, void *dramAddr, u32 size) { + register u32 stat = IO_READ(PI_STATUS_REG); + while (stat & (PI_STATUS_IO_BUSY | PI_STATUS_DMA_BUSY)) { + stat = IO_READ(PI_STATUS_REG); + } + IO_WRITE(PI_DRAM_ADDR_REG, K0_TO_PHYS(dramAddr)); + IO_WRITE(PI_CART_ADDR_REG, K1_TO_PHYS((u32)osRomBase | devAddr)); + IO_WRITE(PI_WR_LEN_REG, size - 1); +} +static u32 headless_pi_status(void) { + return IO_READ(PI_STATUS_REG); +} +// end of code provided by Wiseguy + +void map_parser_dma(void *dst, void *src, size_t size) { + osWritebackDCacheAll(); + headless_dma((u32)src, dst, size); + while (headless_pi_status() & PI_STATUS_IO_BUSY); + osInvalICache(dst, size); +} + +/** + * @brief Open the SYMT symbol table in the rompak. + * + * If not found, return a null header. + */ +symtable_header_t symt_open(void) { + SYMT_ROM = (u32)_mapDataSegmentRomStart; + + symtable_header_t ALIGNED8 symt_header; + + if (SYMT_ROM == 0) { + return (symtable_header_t){0}; + } + + osWritebackDCache(&symt_header, sizeof(symt_header)); + map_parser_dma( + &symt_header, + (uintptr_t *)SYMT_ROM, + sizeof(symtable_header_t) + ); + + if (symt_header.head[0] != 'S' || symt_header.head[1] != 'Y' || symt_header.head[2] != 'M' || symt_header.head[3] != 'T') { + osSyncPrintf("symt_open: invalid symbol table found at 0x%08lx\n", SYMT_ROM); + SYMT_ROM = 0; + return (symtable_header_t){0}; + } + if (symt_header.version != 2) { + osSyncPrintf("symt_open: unsupported symbol table version %ld -- please update your n64sym tool\n", symt_header.version); + SYMT_ROM = 0; + return (symtable_header_t){0}; + } + + return symt_header; +} + +/** + * @brief Return an entry in the address table by index + * + * @param symt SYMT file header + * @param idx Index of the entry to return + * @return addrtable_entry_t Entry of the address table + */ +addrtable_entry_t symt_addrtab_entry(symtable_header_t *symt, int idx) { + return IO_READ(0xB0000000 | (SYMT_ROM + symt->addrtab_off + idx * 4)); +} + +/** + * @brief Search the SYMT address table for the given address. + * + * Run a binary search to find the entry in the table. If there is a single exact match, + * the entry is returned. If there are multiple entries with the same address, the first + * entry is returned (this is the case for inlined functions: so some entries following + * the current one will have the same address). If there is no exact match, the entry + * with the biggest address just before the given address is returned. + * + * @param symt SYMT file header + * @param addr Address to search for + * @param idx If not null, will be set to the index of the entry found (or the index just before) + * @return The found entry (or the entry just before) + */ +addrtable_entry_t symt_addrtab_search(symtable_header_t *symt, u32 addr, int *idx) { + int min = 0; + int max = symt->addrtab_size - 1; + while (min < max) { + int mid = (min + max) / 2; + addrtable_entry_t entry = symt_addrtab_entry(symt, mid); + if (addr <= ADDRENTRY_ADDR(entry)) { + max = mid; + } else { + min = mid + 1; + } + } + addrtable_entry_t entry = symt_addrtab_entry(symt, min); + if (min > 0 && ADDRENTRY_ADDR(entry) > addr) { + entry = symt_addrtab_entry(symt, --min); + } + if (idx) { + *idx = min; + } + return entry; +} + +/** + * @brief Fetch a string from the string table + * + * @param symt SYMT file + * @param sidx Index of the first character of the string in the string table + * @param slen Length of the string + * @param buf Destination buffer + * @param size Size of the destination buffer + * @return char* Fetched string within the destination buffer (might not be at offset 0 for alignment reasons) + */ +char *symt_string(symtable_header_t *symt, int sidx, int slen, char *buf, int size) { + // Align 2-byte phase of the RAM buffer with the ROM address. This is required + // for map_parser_dma. + int tweak = (sidx ^ (u32)buf) & 1; + char *func = buf + tweak; size -= tweak; + int nbytes = MIN(slen, size); + + osWritebackDCache(buf, size); + map_parser_dma( + func, + (uintptr_t *)(SYMT_ROM + symt->strtab_off + sidx), + size + ); + func[nbytes] = 0; + + if (tweak) { + buf[0] = ' '; + } + return func; +} + +/** + * @brief Fetch a symbol table entry from the SYMT file. + * + * @param symt SYMT file + * @param entry Output entry pointer + * @param idx Index of the entry to fetch + */ +void symt_entry_fetch(symtable_header_t *symt, symtable_entry_t *entry, int idx) { + osWritebackDCache(entry, sizeof(symtable_entry_t)); + + map_parser_dma( + entry, + (uintptr_t *)(SYMT_ROM + symt->symtab_off + idx * sizeof(symtable_entry_t)), + sizeof(symtable_entry_t) + ); +} + +// Fetch the function name of an entry +char *symt_entry_func(symtable_header_t *symt, symtable_entry_t *entry, u32 addr, char *buf, int size) { + if (is_in_exception(addr)) { + // Special case exception handlers. This is just to show something slightly + // more readable instead of "notcart+0x0" or similar assembly symbols + sprintf(buf, ""); + return buf; + } else { + return symt_string(symt, entry->func_sidx, entry->func_len, buf, size); + } +} + +// Fetch the file name of an entry +char *symt_entry_file(symtable_header_t *symt, symtable_entry_t *entry, char *buf, int size) { + return symt_string(symt, entry->file_sidx, entry->file_len, buf, size); +} + +#endif // DEBUG_EXPORT_SYMBOLS diff --git a/src/debugger/symtable.h b/src/debugger/symtable.h new file mode 100644 index 0000000000..1f1d3eba3c --- /dev/null +++ b/src/debugger/symtable.h @@ -0,0 +1,76 @@ +#pragma once + +/** + * @brief Symbol table file header + * + * The SYMT file is made of three main tables: + * + * * Address table: this is a sequence of 32-bit integers, each representing an address in the ROM. + * The table is sorted in ascending order to allow for binary search. Moreover, the lowest 2 bits + * of each address can store additional information: If bit 0 is set to 1, the address is the start + * of a function. If bit 1 is set to 1, the address is an inline duplicate. In fact, there might be + * multiple symbols at the same address for inlined functions, so we need one entry in this table + * for each entry; all of them will have the same address, and all but the last one will have bit + * 1 set to 1. + * * Symbol table: this is a sequence of symbol table entries, each representing a symbol. The size + * of this table (in number of entries) is exactly the same as the address table. In fact, each + * address of the address table can be thought of as an external member of this structure; it's + * split externally to allow for efficiency reasons. Each entry stores the function name, + * the source file name and line number, and the binary offset of the symbol within the containing + * function. + * * String table: this table can be thought as a large buffer holding all the strings needed by all + * symbol entries (function names and file names). Each symbol entry stores a string as an offset + * within the symbol table and a length. This allows to reuse the same string (or prefix thereof) + * multiple times. Notice that strings are not null terminated in the string table. + * + * The SYMT file is generated by the n64sym tool during the build process. + */ +typedef struct { + char head[4]; ///< Magic ID "SYMT" + u32 version; ///< Version of the symbol table + u32 addrtab_off; ///< Offset of the address table in the file + u32 addrtab_size; ///< Size of the address table in the file (number of entries) + u32 symtab_off; ///< Offset of the symbol table in the file + u32 symtab_size; ///< Size of the symbol table in the file (number of entries); always equal to addrtab_size. + u32 strtab_off; ///< Offset of the string table in the file + u32 strtab_size; ///< Size of the string table in the file (number of entries) +} symtable_header_t; + +/** @brief Symbol table entry **/ +typedef struct { + u32 func_sidx; ///< Offset of the function name in the string table + u32 file_sidx; ///< Offset of the file name in the string table + u16 func_len; ///< Length of the function name + u16 file_len; ///< Length of the file name + u16 line; ///< Line number (or 0 if this symbol generically refers to a whole function) + u16 func_off; ///< Offset of the symbol within its function +} symtable_entry_t; + +/** + * @brief Entry in the address table. + * + * This is an address in RAM, with the lowest 2 bits used to store additional information. + * See the ADDRENTRY_* macros to access the various components. + */ +typedef u32 addrtable_entry_t; + +#define ADDRENTRY_ADDR(e) ((e) & ~3) ///< Address (without the flags) +#define ADDRENTRY_IS_FUNC(e) ((e) & 1) ///< TRUE if the address is the start of a function +#define ADDRENTRY_IS_INLINE(e) ((e) & 2) ///< TRUE if the address is an inline duplicate + +#define MIPS_OP_ADDIU_SP(op) (((op) & 0xFFFF0000) == 0x27BD0000) ///< Matches: addiu $sp, $sp, imm +#define MIPS_OP_DADDIU_SP(op) (((op) & 0xFFFF0000) == 0x67BD0000) ///< Matches: daddiu $sp, $sp, imm +#define MIPS_OP_JR_RA(op) (((op) & 0xFFFFFFFF) == 0x03E00008) ///< Matches: jr $ra +#define MIPS_OP_SD_RA_SP(op) (((op) & 0xFFFF0000) == 0xAFBF0000) ///< Matches: sw $ra, imm($sp) +#define MIPS_OP_SD_FP_SP(op) (((op) & 0xFFFF0000) == 0xAFBE0000) ///< Matches: sw $fp, imm($sp) +#define MIPS_OP_LUI_GP(op) (((op) & 0xFFFF0000) == 0x3C1C0000) ///< Matches: lui $gp, imm +#define MIPS_OP_NOP(op) ((op) == 0x00000000) ///< Matches: nop +#define MIPS_OP_MOVE_FP_SP(op) ((op) == 0x03A0F025) ///< Matches: move $fp, $sp + +symtable_header_t symt_open(void); +addrtable_entry_t symt_addrtab_entry(symtable_header_t *symt, int idx); +addrtable_entry_t symt_addrtab_search(symtable_header_t *symt, u32 addr, int *idx); +char *symt_string(symtable_header_t *symt, int sidx, int slen, char *buf, int size); +void symt_entry_fetch(symtable_header_t *symt, symtable_entry_t *entry, int idx); +char *symt_entry_func(symtable_header_t *symt, symtable_entry_t *entry, u32 addr, char *buf, int size); +char *symt_entry_file(symtable_header_t *symt, symtable_entry_t *entry, char *buf, int size); diff --git a/src/engine/geo_layout.c b/src/engine/geo_layout.c index cf217a936d..7a0809c7ba 100644 --- a/src/engine/geo_layout.c +++ b/src/engine/geo_layout.c @@ -4,6 +4,7 @@ #include "geo_layout.h" #include "math_util.h" #include "game/memory.h" +#include "debugger/assert.h" #include "graph_node.h" #include "game/debug.h" @@ -604,7 +605,7 @@ void geo_layout_cmd_node_billboard(void) { cmdPos = read_vec3s(axis, &cmdPos[0]); if (isCylindrical) { - assert(axis[0] != 0 || axis[1] != 0 || axis[2] != 0, "Axis vector for cylindrical billboard\nmust be non-zero"); + assertf(axis[0] != 0 || axis[1] != 0 || axis[2] != 0, "Axis vector for cylindrical billboard\nmust be non-zero"); } if (params & 0x80) { @@ -784,7 +785,7 @@ struct GraphNode *process_geo_layout(struct AllocOnlyPool *pool, void *segptr) { gGeoLayoutStack[1] = 0; while (gGeoLayoutCommand != NULL) { - assert((gGeoLayoutCommand[0x00] < GEO_CMD_COUNT), "Invalid or unloaded geo layout detected."); + assertf((gGeoLayoutCommand[0x00] < GEO_CMD_COUNT), "Invalid or unloaded geo layout detected."); GeoLayoutJumpTable[gGeoLayoutCommand[0x00]](); } diff --git a/src/engine/level_script.c b/src/engine/level_script.c index a6246c0dc4..c62c35b575 100644 --- a/src/engine/level_script.c +++ b/src/engine/level_script.c @@ -8,6 +8,7 @@ #include "audio/synthesis.h" #include "buffers/framebuffers.h" #include "buffers/zbuffer.h" +#include "debugger/assert.h" #include "game/area.h" #include "game/debug.h" #include "game/game_init.h" @@ -439,7 +440,7 @@ static void level_cmd_load_model_from_dl(void) { s16 layer = CMD_GET(u16, 0x8); void *dl_ptr = CMD_GET(void *, 4); - assert_args(model < MODEL_ID_COUNT, "Tried to load an invalid model ID: 0x%04X", model); + assertf(model < MODEL_ID_COUNT, "Tried to load an invalid model ID: 0x%04X", model); if (model < MODEL_ID_COUNT) { gLoadedGraphNodes[model] = (struct GraphNode *) init_graph_node_display_list(sLevelPool, 0, layer, dl_ptr); @@ -452,7 +453,7 @@ static void level_cmd_load_model_from_geo(void) { ModelID16 model = CMD_GET(ModelID16, 2); void *geo = CMD_GET(void *, 4); - assert_args(model < MODEL_ID_COUNT, "Tried to load an invalid model ID: 0x%04X", model); + assertf(model < MODEL_ID_COUNT, "Tried to load an invalid model ID: 0x%04X", model); if (model < MODEL_ID_COUNT) { gLoadedGraphNodes[model] = process_geo_layout(sLevelPool, geo); } @@ -466,7 +467,7 @@ static void level_cmd_23(void) { void *dl = CMD_GET(void *, 4); s32 scale = CMD_GET(s32, 8); - assert_args(model < MODEL_ID_COUNT, "Tried to load an invalid model ID: 0x%04X", model); + assertf(model < MODEL_ID_COUNT, "Tried to load an invalid model ID: 0x%04X", model); if (model < MODEL_ID_COUNT) { // GraphNodeScale has a GraphNode at the top. This // is being stored to the array, so cast the pointer. diff --git a/src/engine/surface_load.c b/src/engine/surface_load.c index 0134e23a56..70d6040ba3 100644 --- a/src/engine/surface_load.c +++ b/src/engine/surface_load.c @@ -5,6 +5,7 @@ #include "graph_node.h" #include "behavior_script.h" #include "behavior_data.h" +#include "debugger/assert.h" #include "game/memory.h" #include "game/object_helpers.h" #include "game/macro_special_objects.h" diff --git a/src/game/crash_screen.c b/src/game/crash_screen.c deleted file mode 100644 index bf0bb88e0c..0000000000 --- a/src/game/crash_screen.c +++ /dev/null @@ -1,487 +0,0 @@ -#include -#include -#include -#include -#include "buffers/framebuffers.h" -#include "types.h" -#include "puppyprint.h" -#include "audio/external.h" -#include "farcall.h" -#include "game_init.h" -#include "main.h" -#include "debug.h" -#include "rumble_init.h" - -#include "sm64.h" - -#include "printf.h" - -#define X_KERNING 6 - -enum crashPages { - PAGE_CONTEXT, -#ifdef PUPPYPRINT_DEBUG - PAGE_LOG, -#endif - PAGE_STACKTRACE, - PAGE_DISASM, - PAGE_ASSERTS, - PAGE_COUNT -}; - -u8 gCrashScreenCharToGlyph[128] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 41, -1, -1, -1, 43, -1, -1, 37, 38, -1, 42, - -1, 39, 44, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 36, -1, -1, -1, -1, 40, -1, 10, - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, - 33, 34, 35, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -}; - -// A height of seven pixels for each Character * nine rows of characters + one row unused. -u32 gCrashScreenFont[7 * 9 + 1] = { - #include "textures/crash_custom/crash_screen_font.ia1.inc.c" -}; - -u8 crashPage = 0; -u8 updateBuffer = TRUE; - -static char crashScreenBuf[0x200]; - -char *gCauseDesc[18] = { - "Interrupt", - "TLB modification", - "TLB exception on load", - "TLB exception on store", - "Address error on load", - "Address error on store", - "Bus error on inst.", - "Bus error on data", - "Failed Assert: See Assert Page", - "Breakpoint exception", - "Reserved instruction", - "Coprocessor unusable", - "Arithmetic overflow", - "Trap exception", - "Virtual coherency on inst.", - "Floating point exception", - "Watchpoint exception", - "Virtual coherency on data", -}; - -char *gFpcsrDesc[6] = { - "Unimplemented operation", "Invalid operation", "Division by zero", "Overflow", "Underflow", - "Inexact operation", -}; - - - -extern u64 osClockRate; -extern far char *parse_map(u32 pc); -extern far void map_data_init(void); -extern far char *find_function_in_stack(u32 *sp); - -struct { - OSThread thread; - u64 stack[THREAD2_STACK / sizeof(u64)]; - OSMesgQueue mesgQueue; - OSMesg mesg; - u16 *framebuffer; - u16 width; - u16 height; -} gCrashScreen; - -void crash_screen_draw_rect(s32 x, s32 y, s32 w, s32 h) { - u16 *ptr; - s32 i, j; - - ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; - for (i = 0; i < h; i++) { - for (j = 0; j < w; j++) { - // 0xe738 = 0b1110011100111000 - *ptr = ((*ptr & 0xe738) >> 2) | 1; - ptr++; - } - ptr += gCrashScreen.width - w; - } -} - -void crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { - const u32 *data; - u16 *ptr; - u32 bit; - u32 rowMask; - s32 i, j; - - data = &gCrashScreenFont[glyph / 5 * 7]; - ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; - - for (i = 0; i < 7; i++) { - bit = 0x80000000U >> ((glyph % 5) * 6); - rowMask = *data++; - - for (j = 0; j < 6; j++) { - *ptr++ = (bit & rowMask) ? 0xffff : 1; - bit >>= 1; - } - ptr += gCrashScreen.width - 6; - } -} - -static char *write_to_buf(char *buffer, const char *data, size_t size) { - return (char *) memcpy(buffer, data, size) + size; -} - -void crash_screen_print_with_newlines(s32 x, s32 y, const s32 xNewline, const char *fmt, ...) { - char *ptr; - u32 glyph; - s32 size; - s32 xOffset = x; - - va_list args; - va_start(args, fmt); - - size = _Printf(write_to_buf, crashScreenBuf, fmt, args); - - if (size > 0) { - ptr = crashScreenBuf; - - while (*ptr && size-- > 0) { - if (xOffset >= SCREEN_WIDTH - (xNewline + X_KERNING)) { - y += 10; - xOffset = xNewline; - } - - glyph = gCrashScreenCharToGlyph[*ptr & 0x7f]; - - if (*ptr == '\n') { - y += 10; - xOffset = x; - ptr++; - continue; - } else if (glyph != 0xff) { - crash_screen_draw_glyph(xOffset, y, glyph); - } - - ptr++; - xOffset += X_KERNING; - } - } - - va_end(args); -} - -void crash_screen_print(s32 x, s32 y, const char *fmt, ...) { - char *ptr; - u32 glyph; - s32 size; - - va_list args; - va_start(args, fmt); - - size = _Printf(write_to_buf, crashScreenBuf, fmt, args); - - if (size > 0) { - ptr = crashScreenBuf; - - while (*ptr && size-- > 0) { - glyph = gCrashScreenCharToGlyph[*ptr & 0x7f]; - - if (glyph != 0xff) { - crash_screen_draw_glyph(x, y, glyph); - } - - ptr++; - x += X_KERNING; - } - } - - va_end(args); -} - -void crash_screen_sleep(s32 ms) { - u64 cycles = ms * 1000LL * osClockRate / 1000000ULL; - osSetTime(0); - while (osGetTime() < cycles) { } -} - -void crash_screen_print_float_reg(s32 x, s32 y, s32 regNum, void *addr) { - u32 bits = *(u32 *) addr; - s32 exponent = ((bits & 0x7f800000U) >> 0x17) - 0x7F; - - if ((exponent >= -0x7E && exponent <= 0x7F) || bits == 0x0) { - crash_screen_print(x, y, "F%02d:%.3e", regNum, *(f32 *) addr); - } else { - crash_screen_print(x, y, "F%02d:%08XD", regNum, *(u32 *) addr); - } -} - -void crash_screen_print_fpcsr(u32 fpcsr) { - s32 i; - u32 bit = BIT(17); - - crash_screen_print(30, 155, "FPCSR:%08XH", fpcsr); - for (i = 0; i < 6; i++) { - if (fpcsr & bit) { - crash_screen_print(132, 155, "(%s)", gFpcsrDesc[i]); - return; - } - bit >>= 1; - } -} - -void draw_crash_context(OSThread *thread, s32 cause) { - __OSThreadContext *tc = &thread->context; - crash_screen_draw_rect(25, 20, 270, 210); - crash_screen_print(30, 20, "THREAD:%d (%s)", thread->id, gCauseDesc[cause]); - crash_screen_print(30, 30, "PC:%08XH SR:%08XH VA:%08XH", tc->pc, tc->sr, tc->badvaddr); - osWritebackDCacheAll(); - if ((u32)parse_map != MAP_PARSER_ADDRESS) { - char *fname = parse_map(tc->pc); - crash_screen_print(30, 40, "CRASH AT: %s", fname == NULL ? "UNKNOWN" : fname); - } - crash_screen_print(30, 50, "AT:%08XH V0:%08XH V1:%08XH", (u32) tc->at, (u32) tc->v0, (u32) tc->v1); - crash_screen_print(30, 60, "A0:%08XH A1:%08XH A2:%08XH", (u32) tc->a0, (u32) tc->a1, (u32) tc->a2); - crash_screen_print(30, 70, "A3:%08XH T0:%08XH T1:%08XH", (u32) tc->a3, (u32) tc->t0, (u32) tc->t1); - crash_screen_print(30, 80, "T2:%08XH T3:%08XH T4:%08XH", (u32) tc->t2, (u32) tc->t3, (u32) tc->t4); - crash_screen_print(30, 90, "T5:%08XH T6:%08XH T7:%08XH", (u32) tc->t5, (u32) tc->t6, (u32) tc->t7); - crash_screen_print(30, 100, "S0:%08XH S1:%08XH S2:%08XH", (u32) tc->s0, (u32) tc->s1, (u32) tc->s2); - crash_screen_print(30, 110, "S3:%08XH S4:%08XH S5:%08XH", (u32) tc->s3, (u32) tc->s4, (u32) tc->s5); - crash_screen_print(30, 120, "S6:%08XH S7:%08XH T8:%08XH", (u32) tc->s6, (u32) tc->s7, (u32) tc->t8); - crash_screen_print(30, 130, "T9:%08XH GP:%08XH SP:%08XH", (u32) tc->t9, (u32) tc->gp, (u32) tc->sp); - crash_screen_print(30, 140, "S8:%08XH RA:%08XH", (u32) tc->s8, (u32) tc->ra); - crash_screen_print_fpcsr(tc->fpcsr); - - osWritebackDCacheAll(); - crash_screen_print_float_reg( 30, 170, 0, &tc->fp0.f.f_even); - crash_screen_print_float_reg(120, 170, 2, &tc->fp2.f.f_even); - crash_screen_print_float_reg(210, 170, 4, &tc->fp4.f.f_even); - crash_screen_print_float_reg( 30, 180, 6, &tc->fp6.f.f_even); - crash_screen_print_float_reg(120, 180, 8, &tc->fp8.f.f_even); - crash_screen_print_float_reg(210, 180, 10, &tc->fp10.f.f_even); - crash_screen_print_float_reg( 30, 190, 12, &tc->fp12.f.f_even); - crash_screen_print_float_reg(120, 190, 14, &tc->fp14.f.f_even); - crash_screen_print_float_reg(210, 190, 16, &tc->fp16.f.f_even); - crash_screen_print_float_reg( 30, 200, 18, &tc->fp18.f.f_even); - crash_screen_print_float_reg(120, 200, 20, &tc->fp20.f.f_even); - crash_screen_print_float_reg(210, 200, 22, &tc->fp22.f.f_even); - crash_screen_print_float_reg( 30, 210, 24, &tc->fp24.f.f_even); - crash_screen_print_float_reg(120, 210, 26, &tc->fp26.f.f_even); - crash_screen_print_float_reg(210, 210, 28, &tc->fp28.f.f_even); - crash_screen_print_float_reg( 30, 220, 30, &tc->fp30.f.f_even); -} - - -#ifdef PUPPYPRINT_DEBUG -void draw_crash_log(void) { - s32 i; - crash_screen_draw_rect(25, 20, 270, 210); - osWritebackDCacheAll(); -#define LINE_HEIGHT (25 + ((LOG_BUFFER_SIZE - 1) * 10)) - for (i = 0; i < LOG_BUFFER_SIZE; i++) { - crash_screen_print(30, (LINE_HEIGHT - (i * 10)), consoleLogTable[i]); - } -#undef LINE_HEIGHT -} -#endif - - -// prints any function pointers it finds in the stack format: -// SP address: function name -void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { - __OSThreadContext *tc = &thread->context; - u32 temp_sp = (tc->sp + 0x14); - - crash_screen_draw_rect(25, 20, 270, 210); - crash_screen_print(30, 25, "STACK TRACE FROM %08X:", temp_sp); - if ((u32) parse_map == MAP_PARSER_ADDRESS) { - crash_screen_print(30, 35, "CURRFUNC: NONE"); - } else { - crash_screen_print(30, 35, "CURRFUNC: %s", parse_map(tc->pc)); - } - - osWritebackDCacheAll(); - - for (int i = 0; i < 18; i++) { - if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { - crash_screen_print(30, (45 + (i * 10)), "STACK TRACE DISABLED"); - break; - } else { - if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { - return; - } - - char *fname = find_function_in_stack(&temp_sp); - if ((fname == NULL) || ((*(u32*)temp_sp & 0x80000000) == 0)) { - crash_screen_print(30, (45 + (i * 10)), "%08X: UNKNOWN", temp_sp); - } else { - crash_screen_print(30, (45 + (i * 10)), "%08X: %s", temp_sp, fname); - } - } - } -} - -extern char *insn_disasm(u32 insn, u32 isPC); -static u32 sProgramPosition = 0; -void draw_disasm(OSThread *thread) { - __OSThreadContext *tc = &thread->context; - // u32 insn = *(u32*)tc->pc; - - crash_screen_draw_rect(25, 20, 270, 210); - if (sProgramPosition == 0) { - sProgramPosition = (tc->pc - 36); - } - crash_screen_print(30, 25, "DISASM %08X", sProgramPosition); - osWritebackDCacheAll(); - - - for (int i = 0; i < 19; i++) { - u32 addr = (sProgramPosition + (i * 4)); - u32 toDisasm = *(u32*)(addr); - - crash_screen_print(30, (35 + (i * 10)), "%s", insn_disasm(toDisasm, (addr == tc->pc))); - } - - osWritebackDCacheAll(); -} - -void draw_assert(UNUSED OSThread *thread) { - crash_screen_draw_rect(25, 20, 270, 210); - - crash_screen_print(30, 25, "ASSERT PAGE"); - - if (__n64Assert_Filename != NULL) { - crash_screen_print(30, 45, "FILE: %s", __n64Assert_Filename); - crash_screen_print(30, 55, "LINE: %d", __n64Assert_LineNum); - crash_screen_print(30, 75, "MESSAGE:", __n64Assert_Message); - crash_screen_print_with_newlines(36, 85, 30, __n64Assert_Message); - } else { - crash_screen_print(30, 45, "No failed assert to report."); - } - - osWritebackDCacheAll(); -} - -void draw_crash_screen(OSThread *thread) { - __OSThreadContext *tc = &thread->context; - - s32 cause = ((tc->cause >> 2) & 0x1F); - if (cause == 23) { // EXC_WATCH - cause = 16; - } - if (cause == 31) { // EXC_VCED - cause = 17; - } - - if (gPlayer1Controller->buttonPressed & R_TRIG) { - crashPage++; - updateBuffer = TRUE; - } - if (gPlayer1Controller->buttonPressed & (L_TRIG | Z_TRIG)) { - crashPage--; - updateBuffer = TRUE; - } - if (gPlayer1Controller->buttonDown & D_CBUTTONS) { - sProgramPosition += 4; - updateBuffer = TRUE; - } - if (gPlayer1Controller->buttonDown & U_CBUTTONS) { - sProgramPosition -= 4; - updateBuffer = TRUE; - } - - if ((crashPage >= PAGE_COUNT) && (crashPage != 255)) { - crashPage = 0; - } - if (crashPage == 255) { - crashPage = (PAGE_COUNT - 1); - } - if (updateBuffer) { - crash_screen_draw_rect(25, 8, 270, 12); - crash_screen_print(30, 10, "Page:%02d L/Z: Left R: Right", crashPage); - switch (crashPage) { - case PAGE_CONTEXT: draw_crash_context(thread, cause); break; -#ifdef PUPPYPRINT_DEBUG - case PAGE_LOG: draw_crash_log(); break; -#endif - case PAGE_STACKTRACE: draw_stacktrace(thread, cause); break; - case PAGE_DISASM: draw_disasm(thread); break; - case PAGE_ASSERTS: draw_assert(thread); break; - } - - osWritebackDCacheAll(); - osViBlack(FALSE); - osViSwapBuffer(gCrashScreen.framebuffer); - updateBuffer = FALSE; - } -} - -OSThread *get_crashed_thread(void) { - OSThread *thread = __osGetCurrFaultedThread(); - - while (thread->priority != -1) { - if (thread->priority > OS_PRIORITY_IDLE && thread->priority < OS_PRIORITY_APPMAX - && ((thread->flags & (BIT(0) | BIT(1))) != 0)) { - return thread; - } - thread = thread->tlnext; - } - return NULL; -} - -extern u16 sRenderedFramebuffer; -extern void audio_signal_game_loop_tick(void); -extern void stop_sounds_in_continuous_banks(void); -extern void read_controller_inputs(s32 threadID); -extern struct SequenceQueueItem sBackgroundMusicQueue[6]; - -void thread2_crash_screen(UNUSED void *arg) { - OSMesg mesg; - OSThread *thread = NULL; - - osSetEventMesg(OS_EVENT_CPU_BREAK, &gCrashScreen.mesgQueue, (OSMesg) 1); - osSetEventMesg(OS_EVENT_FAULT, &gCrashScreen.mesgQueue, (OSMesg) 2); - while (TRUE) { - if (thread == NULL) { - osRecvMesg(&gCrashScreen.mesgQueue, &mesg, 1); - thread = get_crashed_thread(); - gCrashScreen.framebuffer = (RGBA16 *) gFramebuffers[sRenderedFramebuffer]; - if (thread) { - if ((u32) map_data_init != MAP_PARSER_ADDRESS) { - map_data_init(); - } - gCrashScreen.thread.priority = 15; - stop_sounds_in_continuous_banks(); - stop_background_music(sBackgroundMusicQueue[0].seqId); - audio_signal_game_loop_tick(); - crash_screen_sleep(200); - play_sound(SOUND_MARIO_WAAAOOOW, gGlobalSoundSource); - audio_signal_game_loop_tick(); - crash_screen_sleep(200); - continue; - } - } else { - if (gControllerBits) { -#if ENABLE_RUMBLE - block_until_rumble_pak_free(); -#endif - osContStartReadDataEx(&gSIEventMesgQueue); - } - read_controller_inputs(THREAD_2_CRASH_SCREEN); - draw_crash_screen(thread); - } - } -} - -void crash_screen_init(void) { - gCrashScreen.framebuffer = (RGBA16 *) gFramebuffers[sRenderedFramebuffer]; - gCrashScreen.width = SCREEN_WIDTH; - gCrashScreen.height = SCREEN_HEIGHT; - osCreateMesgQueue(&gCrashScreen.mesgQueue, &gCrashScreen.mesg, 1); - osCreateThread(&gCrashScreen.thread, THREAD_2_CRASH_SCREEN, thread2_crash_screen, NULL, - (u8 *) gCrashScreen.stack + sizeof(gCrashScreen.stack), - OS_PRIORITY_APPMAX - ); - osStartThread(&gCrashScreen.thread); -} - diff --git a/src/game/debug.h b/src/game/debug.h index f7e75b3599..0a3e82887b 100644 --- a/src/game/debug.h +++ b/src/game/debug.h @@ -40,56 +40,4 @@ void try_print_debug_mario_level_info(void); #define try_print_debug_mario_level_info() #endif -extern char *__n64Assert_Filename; -extern u32 __n64Assert_LineNum; -extern char *__n64Assert_Message; -extern void __n64Assert(char *fileName, u32 lineNum, char *message); - -extern char gAssertionStr[0x200]; - -/** - * Will always cause a crash with your message of choice - */ -#define error(message) __n64Assert(__FILE__, __LINE__, (message)) - -/** - * Will always cause a crash if cond is not true (handle with care) - */ -#define aggress(cond, message) do {\ - if ((cond) == FALSE) { \ - error(message); \ - } \ -} while (0); - -#define aggress_args(cond, ...) do {\ - if ((cond) == FALSE) { \ - sprintf(gAssertionStr, __VA_ARGS__); \ - error(gAssertionStr); \ - } \ -} while (0); - -/** - * Will cause a crash if cond is not true, and DEBUG is defined (allows for quick removal of littered asserts) - */ -#ifdef DEBUG_ASSERTIONS - -#define assert(cond, message) do {\ - if ((cond) == FALSE) { \ - error(message); \ - } \ -} while (0); - -#define assert_args(cond, ...) do {\ - if ((cond) == FALSE) { \ - sprintf(gAssertionStr, __VA_ARGS__); \ - error(gAssertionStr); \ - } \ -} while (0); - -#else -#define assert(cond, message) -#define assert_args(cond, ...) -#endif - - #endif // DEBUG_H diff --git a/src/game/game_init.c b/src/game/game_init.c index 2a8de60f8d..f4fa16625e 100644 --- a/src/game/game_init.c +++ b/src/game/game_init.c @@ -7,6 +7,7 @@ #include "buffers/gfx_output_buffer.h" #include "buffers/framebuffers.h" #include "buffers/zbuffer.h" +#include "debugger/assert.h" #include "engine/level_script.h" #include "engine/math_util.h" #include "game_init.h" @@ -351,7 +352,7 @@ void create_gfx_task_structure(void) { gGfxSPTask->task.t.yield_data_size = OS_YIELD_DATA_SIZE; // NOTE: 'entries' is not representative of the right-side allocations coming from the GFX pool; do not use that variable here. - assert_args((u8*) gDisplayListHead <= gGfxPoolEnd, "GFX pool exceeded: %d command(s) over!", ((s32) gGfxPoolEnd - (s32) gDisplayListHead) / sizeof(Gfx)); + assertf((u8*) gDisplayListHead <= gGfxPoolEnd, "GFX pool exceeded: %d command(s) over!", ((s32) gGfxPoolEnd - (s32) gDisplayListHead) / sizeof(Gfx)); } /** diff --git a/src/game/insn_disasm.c b/src/game/insn_disasm.c deleted file mode 100644 index 5d22acbe34..0000000000 --- a/src/game/insn_disasm.c +++ /dev/null @@ -1,168 +0,0 @@ -#include -#include - -#include "sm64.h" -#include "macros.h" -#include "farcall.h" - -enum InsnTypes { - R_TYPE, - I_TYPE, - J_TYPE, -}; - -enum ParamTypes { - PARAM_NONE, - PARAM_SWAP_RS_IMM, - PARAM_JAL, - PARAM_JR, - PARAM_LUI, -}; - -extern far char *parse_map(u32 pc); -static char insn_as_string[100]; - -typedef struct PACKED { - u16 rd : 5; - u16 shift_amt : 5; - u16 function : 6; -} RTypeData; - -typedef struct PACKED { - u16 opcode : 6; - u16 rs : 5; - u16 rt : 5; - union { - RTypeData rdata; - u16 immediate; - }; -} Insn; - -typedef union { - Insn i; - u32 d; -} InsnData; - -typedef struct PACKED { - u32 type; - u32 arbitraryParam; - u16 opcode : 6; - u16 function : 6; - u8 name[10]; -} InsnTemplate; - - -InsnTemplate insn_db[] = { - {R_TYPE, PARAM_NONE, 0, 0b100000, "ADD"}, - {R_TYPE, PARAM_NONE, 0, 0b100001, "ADDU"}, - {I_TYPE, PARAM_SWAP_RS_IMM, 0b001001, 0, "ADDIU"}, - {R_TYPE, PARAM_NONE, 0, 0b100100, "AND"}, - {R_TYPE, PARAM_NONE, 0, 0b011010, "DIV"}, - {R_TYPE, PARAM_NONE, 0, 0b011011, "DIVU"}, - {R_TYPE, PARAM_NONE, 0, 0b001000, "JR"}, - - {I_TYPE, PARAM_NONE, 0b101000, 0, "SB"}, - {I_TYPE, PARAM_NONE, 0b100000, 0, "LB"}, - {I_TYPE, PARAM_NONE, 0b100100, 0, "LBU"}, - {I_TYPE, PARAM_NONE, 0b101001, 0, "SH"}, - {I_TYPE, PARAM_NONE, 0b100001, 0, "LH"}, - {I_TYPE, PARAM_NONE, 0b100101, 0, "LHU"}, - {I_TYPE, PARAM_NONE, 0b101011, 0, "SW"}, - {I_TYPE, PARAM_NONE, 0b100011, 0, "LW"}, - {I_TYPE, PARAM_LUI, 0b001111, 0, "LUI"}, - - // branches - {I_TYPE, PARAM_SWAP_RS_IMM, 0b000100, 0, "BEQ"}, - {I_TYPE, PARAM_SWAP_RS_IMM, 0b000101, 0, "BNE"}, - {R_TYPE, PARAM_NONE, 0, 0b110100, "TEQ"}, - {R_TYPE, PARAM_NONE, 0, 0b001001, "JALR"}, - - // jal (special) - {J_TYPE, PARAM_JAL, 0b000011, 0, "JAL"} -}; - - -char registerMaps[][4] = { - "$R0", - "$AT", - "$V0", "$V1", - "$A0", "$A1", "$A2", "$A3", - "$T0", "$T1", "$T2", "$T3", "$T4", "$T5", "$T6", "$T7", - "$S0", "$S1", "$S2", "$S3", "$S4", "$S5", "$S6", "$S7", - "$T8", "$T9", - "$K0", "$K1", - "$GP", "$SP", "$FP", "$RA", -}; - -char *insn_disasm(InsnData insn, u32 isPC) { - char *strp = &insn_as_string[0]; - int successful_print = 0; - u32 target; - - if (insn.d == 0) { // trivial case - if (isPC) { - return "NOP <-- CRASH"; - } else { - return "NOP"; - } - } - - for (int i = 0; i < ARRAY_COUNT(insn_as_string); i++) insn_as_string[i] = 0; - - for (int i = 0; i < ARRAY_COUNT(insn_db); i++) { - if (insn.i.opcode != 0 && insn.i.opcode == insn_db[i].opcode) { - switch (insn_db[i].arbitraryParam) { - case PARAM_SWAP_RS_IMM: - strp += sprintf(strp, "%-8s %s %s %04X", insn_db[i].name, - registerMaps[insn.i.rt], - registerMaps[insn.i.rs], - insn.i.immediate - ); break; - case PARAM_LUI: - strp += sprintf(strp, "%-8s %s %04X", insn_db[i].name, - registerMaps[insn.i.rt], - insn.i.immediate - ); break; - break; - case PARAM_JAL: - target = 0x80000000 | ((insn.d & 0x1FFFFFF) * 4); - if ((u32)parse_map != MAP_PARSER_ADDRESS) { - strp += sprintf(strp, "%-8s %s", insn_db[i].name, - parse_map(target) - ); - } else { - strp += sprintf(strp, "%-8s %08X", insn_db[i].name, - target - ); - } - break; - case PARAM_NONE: - strp += sprintf(strp, "%-8s %s %04X (%s)", insn_db[i].name, - registerMaps[insn.i.rt], - insn.i.immediate, - registerMaps[insn.i.rs] - ); break; - - } - successful_print = 1; - break; - } else if (insn.i.rdata.function != 0 && insn.i.rdata.function == insn_db[i].function) { - strp += sprintf(strp, "%-8s %s %s %s", insn_db[i].name, - registerMaps[insn.i.rdata.rd], - registerMaps[insn.i.rs], - registerMaps[insn.i.rt] - ); - successful_print = 1; - break; - } - } - if (successful_print == 0) { - strp += sprintf(strp, "unimpl %08X", insn.d); - } - - if (isPC) { - sprintf(strp, " <-- CRASH"); - } - - return insn_as_string; -} diff --git a/src/game/level_update.c b/src/game/level_update.c index 9219167bef..de420877db 100644 --- a/src/game/level_update.c +++ b/src/game/level_update.c @@ -5,6 +5,7 @@ #include "dialog_ids.h" #include "audio/external.h" #include "audio/synthesis.h" +#include "debugger/assert.h" #include "level_update.h" #include "game_init.h" #include "level_update.h" @@ -350,7 +351,7 @@ void set_mario_initial_action(struct MarioState *m, u32 spawnType, u32 actionArg void init_mario_after_warp(void) { struct Object *object = get_destination_warp_object(sWarpDest.nodeId); - assert_args(object, "No dest warp object found for: 0x%02X", sWarpDest.nodeId); + assertf(object, "No dest warp object found for: 0x%02X", sWarpDest.nodeId); u32 marioSpawnType = get_mario_spawn_type(object); @@ -572,7 +573,7 @@ void check_instant_warp(void) { s16 music_unchanged_through_warp(s16 arg) { struct ObjectWarpNode *warpNode = area_get_warp_node(arg); - assert_args(warpNode, "No source warp node found for: 0x%02X", (u8) arg); + assertf(warpNode, "No source warp node found for: 0x%02X", (u8) arg); s16 levelNum = warpNode->node.destLevel & 0x7F; @@ -900,7 +901,7 @@ void initiate_delayed_warp(void) { default: warpNode = area_get_warp_node(sSourceWarpNodeId); - assert_args(warpNode, "No source warp node found for: 0x%02X", (u8) sSourceWarpNodeId); + assertf(warpNode, "No source warp node found for: 0x%02X", (u8) sSourceWarpNodeId); initiate_warp(warpNode->node.destLevel & 0x7F, warpNode->node.destArea, warpNode->node.destNode, sDelayedWarpArg); diff --git a/src/game/map_parser.c b/src/game/map_parser.c deleted file mode 100644 index dc07a0d21f..0000000000 --- a/src/game/map_parser.c +++ /dev/null @@ -1,86 +0,0 @@ -#include -#include -#include -#include -#include "segments.h" - -#define STACK_TRAVERSAL_LIMIT 100 - -struct MapEntry { - u32 addr; - u32 nm_offset; - u32 nm_len; - u32 pad; -}; -extern u8 gMapStrings[]; -extern struct MapEntry gMapEntries[]; -extern u32 gMapEntrySize; -extern u8 _mapDataSegmentRomStart[]; - - -// code provided by Wiseguy -static void headless_dma(u32 devAddr, void *dramAddr, u32 size) -{ - register u32 stat = IO_READ(PI_STATUS_REG); - while (stat & (PI_STATUS_IO_BUSY | PI_STATUS_DMA_BUSY)) { - stat = IO_READ(PI_STATUS_REG); - } - IO_WRITE(PI_DRAM_ADDR_REG, K0_TO_PHYS(dramAddr)); - IO_WRITE(PI_CART_ADDR_REG, K1_TO_PHYS((u32)osRomBase | devAddr)); - IO_WRITE(PI_WR_LEN_REG, size - 1); -} -static u32 headless_pi_status(void) -{ - return IO_READ(PI_STATUS_REG); -} -// end of code provided by Wiseguy - - -void map_data_init(void) { - headless_dma((u32)_mapDataSegmentRomStart, (u32*)(RAM_END - 0x100000), 0x100000); - while (headless_pi_status() & (PI_STATUS_DMA_BUSY | PI_STATUS_ERROR)); -} - -char *parse_map(u32 pc) { - u32 i; - - for (i = 0; i < gMapEntrySize; i++) { - if (gMapEntries[i].addr >= pc) break; - } - - if (i == gMapEntrySize - 1) { - return NULL; - } else { - return (char*) ((u32)gMapStrings + gMapEntries[i - 1].nm_offset); - } -} - -extern u8 _mainSegmentStart[]; -extern u8 _mainSegmentTextEnd[]; -extern u8 _engineSegmentStart[]; -extern u8 _engineSegmentTextEnd[]; -extern u8 _goddardSegmentStart[]; -extern u8 _goddardSegmentTextEnd[]; - -char *find_function_in_stack(u32 *sp) { - for (int i = 0; i < STACK_TRAVERSAL_LIMIT; i++) { - u32 val = *sp; - val = *(u32 *)val; - *sp += 4; - - if ((val >= (u32)_mainSegmentStart) && (val <= (u32)_mainSegmentTextEnd)) { - return parse_map(val); - } - else if ((val >= (u32)_engineSegmentStart) && (val <= (u32)_engineSegmentTextEnd)) { - return parse_map(val); - } - else if ((val >= (u32)_goddardSegmentStart) && (val <= (u32)_goddardSegmentTextEnd)) { - return parse_map(val); - } - - - } - return NULL; -} - - diff --git a/src/game/object_list_processor.c b/src/game/object_list_processor.c index 87407dd955..a63a0cae01 100644 --- a/src/game/object_list_processor.c +++ b/src/game/object_list_processor.c @@ -5,6 +5,7 @@ #include "behavior_data.h" #include "camera.h" #include "debug.h" +#include "debugger/assert.h" #include "engine/behavior_script.h" #include "engine/graph_node.h" #include "engine/surface_collision.h" @@ -550,7 +551,7 @@ void update_terrain_objects(void) { profiler_update(PROFILER_TIME_DYNAMIC, profiler_get_delta(PROFILER_DELTA_COLLISION) - first); // If the dynamic surface pool has overflowed, throw an error. - assert((uintptr_t)gDynamicSurfacePoolEnd <= (uintptr_t)gDynamicSurfacePool + DYNAMIC_SURFACE_POOL_SIZE, "Dynamic surface pool size exceeded!"); + assertf((uintptr_t)gDynamicSurfacePoolEnd <= (uintptr_t)gDynamicSurfacePool + DYNAMIC_SURFACE_POOL_SIZE, "Dynamic surface pool size exceeded!"); } /** diff --git a/textures/crash_custom/LICENSE b/textures/crash_custom/LICENSE index 08baf3098a..514e0d287f 100644 --- a/textures/crash_custom/LICENSE +++ b/textures/crash_custom/LICENSE @@ -1,361 +1,24 @@ -These textures were adapted from http://uzebox.org/wiki/File:Font6x7.png for use with ultrasm64 - -Creative Commons Legal Code - -Attribution-ShareAlike 3.0 Unported - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR - DAMAGES RESULTING FROM ITS USE. - -License - -THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE -COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY -COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS -AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. - -BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE -TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY -BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS -CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND -CONDITIONS. - -1. Definitions - - a. "Adaptation" means a work based upon the Work, or upon the Work and - other pre-existing works, such as a translation, adaptation, - derivative work, arrangement of music or other alterations of a - literary or artistic work, or phonogram or performance and includes - cinematographic adaptations or any other form in which the Work may be - recast, transformed, or adapted including in any form recognizably - derived from the original, except that a work that constitutes a - Collection will not be considered an Adaptation for the purpose of - this License. For the avoidance of doubt, where the Work is a musical - work, performance or phonogram, the synchronization of the Work in - timed-relation with a moving image ("synching") will be considered an - Adaptation for the purpose of this License. - b. "Collection" means a collection of literary or artistic works, such as - encyclopedias and anthologies, or performances, phonograms or - broadcasts, or other works or subject matter other than works listed - in Section 1(f) below, which, by reason of the selection and - arrangement of their contents, constitute intellectual creations, in - which the Work is included in its entirety in unmodified form along - with one or more other contributions, each constituting separate and - independent works in themselves, which together are assembled into a - collective whole. A work that constitutes a Collection will not be - considered an Adaptation (as defined below) for the purposes of this - License. - c. "Creative Commons Compatible License" means a license that is listed - at https://creativecommons.org/compatiblelicenses that has been - approved by Creative Commons as being essentially equivalent to this - License, including, at a minimum, because that license: (i) contains - terms that have the same purpose, meaning and effect as the License - Elements of this License; and, (ii) explicitly permits the relicensing - of adaptations of works made available under that license under this - License or a Creative Commons jurisdiction license with the same - License Elements as this License. - d. "Distribute" means to make available to the public the original and - copies of the Work or Adaptation, as appropriate, through sale or - other transfer of ownership. - e. "License Elements" means the following high-level license attributes - as selected by Licensor and indicated in the title of this License: - Attribution, ShareAlike. - f. "Licensor" means the individual, individuals, entity or entities that - offer(s) the Work under the terms of this License. - g. "Original Author" means, in the case of a literary or artistic work, - the individual, individuals, entity or entities who created the Work - or if no individual or entity can be identified, the publisher; and in - addition (i) in the case of a performance the actors, singers, - musicians, dancers, and other persons who act, sing, deliver, declaim, - play in, interpret or otherwise perform literary or artistic works or - expressions of folklore; (ii) in the case of a phonogram the producer - being the person or legal entity who first fixes the sounds of a - performance or other sounds; and, (iii) in the case of broadcasts, the - organization that transmits the broadcast. - h. "Work" means the literary and/or artistic work offered under the terms - of this License including without limitation any production in the - literary, scientific and artistic domain, whatever may be the mode or - form of its expression including digital form, such as a book, - pamphlet and other writing; a lecture, address, sermon or other work - of the same nature; a dramatic or dramatico-musical work; a - choreographic work or entertainment in dumb show; a musical - composition with or without words; a cinematographic work to which are - assimilated works expressed by a process analogous to cinematography; - a work of drawing, painting, architecture, sculpture, engraving or - lithography; a photographic work to which are assimilated works - expressed by a process analogous to photography; a work of applied - art; an illustration, map, plan, sketch or three-dimensional work - relative to geography, topography, architecture or science; a - performance; a broadcast; a phonogram; a compilation of data to the - extent it is protected as a copyrightable work; or a work performed by - a variety or circus performer to the extent it is not otherwise - considered a literary or artistic work. - i. "You" means an individual or entity exercising rights under this - License who has not previously violated the terms of this License with - respect to the Work, or who has received express permission from the - Licensor to exercise rights under this License despite a previous - violation. - j. "Publicly Perform" means to perform public recitations of the Work and - to communicate to the public those public recitations, by any means or - process, including by wire or wireless means or public digital - performances; to make available to the public Works in such a way that - members of the public may access these Works from a place and at a - place individually chosen by them; to perform the Work to the public - by any means or process and the communication to the public of the - performances of the Work, including by public digital performance; to - broadcast and rebroadcast the Work by any means including signs, - sounds or images. - k. "Reproduce" means to make copies of the Work by any means including - without limitation by sound or visual recordings and the right of - fixation and reproducing fixations of the Work, including storage of a - protected performance or phonogram in digital form or other electronic - medium. - -2. Fair Dealing Rights. Nothing in this License is intended to reduce, -limit, or restrict any uses free from copyright or rights arising from -limitations or exceptions that are provided for in connection with the -copyright protection under copyright law or other applicable laws. - -3. License Grant. Subject to the terms and conditions of this License, -Licensor hereby grants You a worldwide, royalty-free, non-exclusive, -perpetual (for the duration of the applicable copyright) license to -exercise the rights in the Work as stated below: - - a. to Reproduce the Work, to incorporate the Work into one or more - Collections, and to Reproduce the Work as incorporated in the - Collections; - b. to create and Reproduce Adaptations provided that any such Adaptation, - including any translation in any medium, takes reasonable steps to - clearly label, demarcate or otherwise identify that changes were made - to the original Work. For example, a translation could be marked "The - original work was translated from English to Spanish," or a - modification could indicate "The original work has been modified."; - c. to Distribute and Publicly Perform the Work including as incorporated - in Collections; and, - d. to Distribute and Publicly Perform Adaptations. - e. For the avoidance of doubt: - - i. Non-waivable Compulsory License Schemes. In those jurisdictions in - which the right to collect royalties through any statutory or - compulsory licensing scheme cannot be waived, the Licensor - reserves the exclusive right to collect such royalties for any - exercise by You of the rights granted under this License; - ii. Waivable Compulsory License Schemes. In those jurisdictions in - which the right to collect royalties through any statutory or - compulsory licensing scheme can be waived, the Licensor waives the - exclusive right to collect such royalties for any exercise by You - of the rights granted under this License; and, - iii. Voluntary License Schemes. The Licensor waives the right to - collect royalties, whether individually or, in the event that the - Licensor is a member of a collecting society that administers - voluntary licensing schemes, via that society, from any exercise - by You of the rights granted under this License. - -The above rights may be exercised in all media and formats whether now -known or hereafter devised. The above rights include the right to make -such modifications as are technically necessary to exercise the rights in -other media and formats. Subject to Section 8(f), all rights not expressly -granted by Licensor are hereby reserved. - -4. Restrictions. The license granted in Section 3 above is expressly made -subject to and limited by the following restrictions: - - a. You may Distribute or Publicly Perform the Work only under the terms - of this License. You must include a copy of, or the Uniform Resource - Identifier (URI) for, this License with every copy of the Work You - Distribute or Publicly Perform. You may not offer or impose any terms - on the Work that restrict the terms of this License or the ability of - the recipient of the Work to exercise the rights granted to that - recipient under the terms of the License. You may not sublicense the - Work. You must keep intact all notices that refer to this License and - to the disclaimer of warranties with every copy of the Work You - Distribute or Publicly Perform. When You Distribute or Publicly - Perform the Work, You may not impose any effective technological - measures on the Work that restrict the ability of a recipient of the - Work from You to exercise the rights granted to that recipient under - the terms of the License. This Section 4(a) applies to the Work as - incorporated in a Collection, but this does not require the Collection - apart from the Work itself to be made subject to the terms of this - License. If You create a Collection, upon notice from any Licensor You - must, to the extent practicable, remove from the Collection any credit - as required by Section 4(c), as requested. If You create an - Adaptation, upon notice from any Licensor You must, to the extent - practicable, remove from the Adaptation any credit as required by - Section 4(c), as requested. - b. You may Distribute or Publicly Perform an Adaptation only under the - terms of: (i) this License; (ii) a later version of this License with - the same License Elements as this License; (iii) a Creative Commons - jurisdiction license (either this or a later license version) that - contains the same License Elements as this License (e.g., - Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible - License. If you license the Adaptation under one of the licenses - mentioned in (iv), you must comply with the terms of that license. If - you license the Adaptation under the terms of any of the licenses - mentioned in (i), (ii) or (iii) (the "Applicable License"), you must - comply with the terms of the Applicable License generally and the - following provisions: (I) You must include a copy of, or the URI for, - the Applicable License with every copy of each Adaptation You - Distribute or Publicly Perform; (II) You may not offer or impose any - terms on the Adaptation that restrict the terms of the Applicable - License or the ability of the recipient of the Adaptation to exercise - the rights granted to that recipient under the terms of the Applicable - License; (III) You must keep intact all notices that refer to the - Applicable License and to the disclaimer of warranties with every copy - of the Work as included in the Adaptation You Distribute or Publicly - Perform; (IV) when You Distribute or Publicly Perform the Adaptation, - You may not impose any effective technological measures on the - Adaptation that restrict the ability of a recipient of the Adaptation - from You to exercise the rights granted to that recipient under the - terms of the Applicable License. This Section 4(b) applies to the - Adaptation as incorporated in a Collection, but this does not require - the Collection apart from the Adaptation itself to be made subject to - the terms of the Applicable License. - c. If You Distribute, or Publicly Perform the Work or any Adaptations or - Collections, You must, unless a request has been made pursuant to - Section 4(a), keep intact all copyright notices for the Work and - provide, reasonable to the medium or means You are utilizing: (i) the - name of the Original Author (or pseudonym, if applicable) if supplied, - and/or if the Original Author and/or Licensor designate another party - or parties (e.g., a sponsor institute, publishing entity, journal) for - attribution ("Attribution Parties") in Licensor's copyright notice, - terms of service or by other reasonable means, the name of such party - or parties; (ii) the title of the Work if supplied; (iii) to the - extent reasonably practicable, the URI, if any, that Licensor - specifies to be associated with the Work, unless such URI does not - refer to the copyright notice or licensing information for the Work; - and (iv) , consistent with Ssection 3(b), in the case of an - Adaptation, a credit identifying the use of the Work in the Adaptation - (e.g., "French translation of the Work by Original Author," or - "Screenplay based on original Work by Original Author"). The credit - required by this Section 4(c) may be implemented in any reasonable - manner; provided, however, that in the case of a Adaptation or - Collection, at a minimum such credit will appear, if a credit for all - contributing authors of the Adaptation or Collection appears, then as - part of these credits and in a manner at least as prominent as the - credits for the other contributing authors. For the avoidance of - doubt, You may only use the credit required by this Section for the - purpose of attribution in the manner set out above and, by exercising - Your rights under this License, You may not implicitly or explicitly - assert or imply any connection with, sponsorship or endorsement by the - Original Author, Licensor and/or Attribution Parties, as appropriate, - of You or Your use of the Work, without the separate, express prior - written permission of the Original Author, Licensor and/or Attribution - Parties. - d. Except as otherwise agreed in writing by the Licensor or as may be - otherwise permitted by applicable law, if You Reproduce, Distribute or - Publicly Perform the Work either by itself or as part of any - Adaptations or Collections, You must not distort, mutilate, modify or - take other derogatory action in relation to the Work which would be - prejudicial to the Original Author's honor or reputation. Licensor - agrees that in those jurisdictions (e.g. Japan), in which any exercise - of the right granted in Section 3(b) of this License (the right to - make Adaptations) would be deemed to be a distortion, mutilation, - modification or other derogatory action prejudicial to the Original - Author's honor and reputation, the Licensor will waive or not assert, - as appropriate, this Section, to the fullest extent permitted by the - applicable national law, to enable You to reasonably exercise Your - right under Section 3(b) of this License (right to make Adaptations) - but not otherwise. - -5. Representations, Warranties and Disclaimer - -UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR -OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY -KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, -INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, -FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF -LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, -WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION -OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. - -6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE -LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR -ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES -ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS -BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. Termination - - a. This License and the rights granted hereunder will terminate - automatically upon any breach by You of the terms of this License. - Individuals or entities who have received Adaptations or Collections - from You under this License, however, will not have their licenses - terminated provided such individuals or entities remain in full - compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will - survive any termination of this License. - b. Subject to the above terms and conditions, the license granted here is - perpetual (for the duration of the applicable copyright in the Work). - Notwithstanding the above, Licensor reserves the right to release the - Work under different license terms or to stop distributing the Work at - any time; provided, however that any such election will not serve to - withdraw this License (or any other license that has been, or is - required to be, granted under the terms of this License), and this - License will continue in full force and effect unless terminated as - stated above. - -8. Miscellaneous - - a. Each time You Distribute or Publicly Perform the Work or a Collection, - the Licensor offers to the recipient a license to the Work on the same - terms and conditions as the license granted to You under this License. - b. Each time You Distribute or Publicly Perform an Adaptation, Licensor - offers to the recipient a license to the original Work on the same - terms and conditions as the license granted to You under this License. - c. If any provision of this License is invalid or unenforceable under - applicable law, it shall not affect the validity or enforceability of - the remainder of the terms of this License, and without further action - by the parties to this agreement, such provision shall be reformed to - the minimum extent necessary to make such provision valid and - enforceable. - d. No term or provision of this License shall be deemed waived and no - breach consented to unless such waiver or consent shall be in writing - and signed by the party to be charged with such waiver or consent. - e. This License constitutes the entire agreement between the parties with - respect to the Work licensed here. There are no understandings, - agreements or representations with respect to the Work not specified - here. Licensor shall not be bound by any additional provisions that - may appear in any communication from You. This License may not be - modified without the mutual written agreement of the Licensor and You. - f. The rights granted under, and the subject matter referenced, in this - License were drafted utilizing the terminology of the Berne Convention - for the Protection of Literary and Artistic Works (as amended on - September 28, 1979), the Rome Convention of 1961, the WIPO Copyright - Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 - and the Universal Copyright Convention (as revised on July 24, 1971). - These rights and subject matter take effect in the relevant - jurisdiction in which the License terms are sought to be enforced - according to the corresponding provisions of the implementation of - those treaty provisions in the applicable national law. If the - standard suite of rights granted under applicable copyright law - includes additional rights not granted under this License, such - additional rights are deemed to be included in the License; this - License is not intended to restrict the license of any rights under - applicable law. - - -Creative Commons Notice - - Creative Commons is not a party to this License, and makes no warranty - whatsoever in connection with the Work. Creative Commons will not be - liable to You or any party on any legal theory for any damages - whatsoever, including without limitation any general, special, - incidental or consequential damages arising in connection to this - license. Notwithstanding the foregoing two (2) sentences, if Creative - Commons has expressly identified itself as the Licensor hereunder, it - shall have all rights and obligations of Licensor. - - Except for the limited purpose of indicating to the public that the - Work is licensed under the CCPL, Creative Commons does not authorize - the use by either party of the trademark "Creative Commons" or any - related trademark or logo of Creative Commons without the prior - written consent of Creative Commons. Any permitted use will be in - compliance with Creative Commons' then-current trademark usage - guidelines, as may be published on its website or otherwise made - available upon request from time to time. For the avoidance of doubt, - this trademark restriction does not form part of the License. - - Creative Commons may be contacted at https://creativecommons.org/. +These textures were adapted from https://tobiasjung.name/profont/ for use with HackerSM64 + +ProFont +MIT License + +Copyright (c) 2014 Carl Osterwald, Stephen C. Gilardi, Andrew Welch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/textures/crash_custom/crash_screen_font.ia1.png b/textures/crash_custom/crash_screen_font.ia1.png index 3a9a899e72..7191dfcdce 100644 Binary files a/textures/crash_custom/crash_screen_font.ia1.png and b/textures/crash_custom/crash_screen_font.ia1.png differ diff --git a/tools/.gitignore b/tools/.gitignore index 72215f5e27..a391936df7 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -7,6 +7,7 @@ /lz4tpack /mio0 /n64cksum +/n64sym /n64graphics /n64graphics_ci /patch_elf_32bit diff --git a/tools/Makefile b/tools/Makefile index e23c339546..17bfc05cc6 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -7,11 +7,19 @@ CC := gcc CXX := g++ CFLAGS := -I. -O2 -s LDFLAGS := -lm -ALL_PROGRAMS := armips filesizer lz4tpack deflatepack rncpack n64graphics n64graphics_ci mio0 slienc n64cksum textconv aifc_decode aiff_extract_codebook vadpcm_enc tabledesign extract_data_for_mio skyconv +ALL_PROGRAMS := armips filesizer lz4tpack deflatepack rncpack n64graphics n64graphics_ci n64sym mio0 slienc n64cksum textconv aifc_decode aiff_extract_codebook vadpcm_enc tabledesign extract_data_for_mio skyconv LIBAUDIOFILE := audiofile/libaudiofile.a OS := $(shell uname) +ifneq (,$(findstring Windows_NT,$(OS))) + OS := Windows_NT +else ifneq (,$(findstring MSYS,$(OS))) + OS := Windows_NT +else ifneq (,$(findstring MINGW,$(OS))) + OS := Windows_NT +endif + ifneq ($(OS), Darwin) ALL_PROGRAMS += flips endif @@ -59,6 +67,11 @@ n64cksum_CFLAGS := -DN64CKSUM_STANDALONE textconv_SOURCES := textconv.c utf8.c hashtable.c +n64sym_SOURCES := n64sym.c +n64sym_CFLAGS := -Wno-unused-result + +patch_elf_32bit_SOURCES := patch_elf_32bit.c + aifc_decode_SOURCES := aifc_decode.c aiff_extract_codebook: $(LIBAUDIOFILE) diff --git a/tools/common/binout.c b/tools/common/binout.c new file mode 100644 index 0000000000..b244370eac --- /dev/null +++ b/tools/common/binout.c @@ -0,0 +1,295 @@ +#include +#include +#include +#include +#include "binout.h" + +#define STBDS_NO_SHORT_NAMES +#define STB_DS_IMPLEMENTATION //Hack to get tools to compile +#include "stb_ds.h" + +struct placeholder_data { + int64_t offset; + uint64_t *pending_offsets_64; + uint32_t *pending_offsets_32; + uint16_t *pending_offsets_16; + uint8_t *pending_offsets_8; +}; + +struct { + char *key; + struct placeholder_data value; +} *placeholder_hash = NULL; + +void _w8(FILE *f, uint8_t v) +{ + fputc(v, f); +} + +void _w16(FILE *f, uint16_t v) +{ + w8(f, v >> 8); + w8(f, v & 0xff); +} + +void _w32(FILE *f, uint32_t v) +{ + w16(f, v >> 16); + w16(f, v & 0xffff); +} + +void _w64(FILE *f, uint64_t v) +{ + w32(f, v >> 32); + w32(f, v & 0xffffffff); +} + +int w32_placeholder(FILE *f) +{ + int pos = ftell(f); + w32(f, 0); + return pos; +} + +void _w64_at(FILE *f, int pos, uint64_t v) +{ + int cur = ftell(f); + assert(cur >= 0); // fail on pipes + fseek(f, pos, SEEK_SET); + w64(f, v); + fseek(f, cur, SEEK_SET); +} + +void _w32_at(FILE *f, int pos, uint32_t v) +{ + int cur = ftell(f); + assert(cur >= 0); // fail on pipes + fseek(f, pos, SEEK_SET); + w32(f, v); + fseek(f, cur, SEEK_SET); +} + +void _w16_at(FILE *f, int pos, uint16_t v) +{ + int cur = ftell(f); + assert(cur >= 0); // fail on pipes + fseek(f, pos, SEEK_SET); + w16(f, v); + fseek(f, cur, SEEK_SET); +} + +void _w8_at(FILE *f, int pos, uint8_t v) +{ + int cur = ftell(f); + assert(cur >= 0); // fail on pipes + fseek(f, pos, SEEK_SET); + w8(f, v); + fseek(f, cur, SEEK_SET); +} + +void walign(FILE *f, int align) +{ + int pos = ftell(f); + assert(pos >= 0); // fail on pipes + while (pos++ % align) w8(f, 0); +} + +void wpad(FILE *f, int size) +{ + while (size--) { + w8(f, 0); + } +} + +struct placeholder_data *__placeholder_get_data(const char *name) +{ + if(placeholder_hash == NULL) { + stbds_sh_new_arena(placeholder_hash); + } + ptrdiff_t index = stbds_shgeti(placeholder_hash, name); + if(index == -1) { + struct placeholder_data default_value = { + .offset = -1, + .pending_offsets_64 = NULL, + .pending_offsets_32 = NULL, + .pending_offsets_16 = NULL, + .pending_offsets_8 = NULL, + }; + index = stbds_shlen(placeholder_hash); + stbds_shput(placeholder_hash, name, default_value); + } + return &placeholder_hash[index].value; +} + +void __placeholder_make(FILE *file, int64_t offset, const char *name) +{ + struct placeholder_data *data = __placeholder_get_data(name); + data->offset = offset; + for (int i=0; ipending_offsets_64); i++) { + w32_at(file, data->pending_offsets_64[i], data->offset); + } + for(int i=0; ipending_offsets_32); i++) { + w32_at(file, data->pending_offsets_32[i], data->offset); + } + for(int i=0; ipending_offsets_16); i++) { + w16_at(file, data->pending_offsets_16[i], data->offset); + } + for(int i=0; ipending_offsets_8); i++) { + w8_at(file, data->pending_offsets_8[i], data->offset); + } + stbds_arrfree(data->pending_offsets_64); + stbds_arrfree(data->pending_offsets_32); + stbds_arrfree(data->pending_offsets_16); + stbds_arrfree(data->pending_offsets_8); +} + +void placeholder_setv(FILE *file, const char *format, va_list arg) +{ + char *name = NULL; + vasprintf(&name, format, arg); + __placeholder_make(file, ftell(file), name); + free(name); +} + +void placeholder_set(FILE *file, const char *format, ...) +{ + va_list args; + va_start(args, format); + placeholder_setv(file, format, args); + va_end(args); +} + +void placeholder_setv_offset(FILE *file, int64_t offset, const char *format, va_list arg) +{ + char *name = NULL; + vasprintf(&name, format, arg); + __placeholder_make(file, offset, name); + free(name); +} + +void placeholder_set_offset(FILE *file, int64_t offset, const char *format, ...) +{ + va_list args; + va_start(args, format); + placeholder_setv_offset(file, offset, format, args); + va_end(args); +} + +void __w64_placeholder_named(FILE *file, const char *name) +{ + struct placeholder_data *data = __placeholder_get_data(name); + if(data->offset == -1) { + stbds_arrpush(data->pending_offsets_64, ftell(file)); + w64(file, 0); + } else { + w64(file, data->offset); + } +} + +void __w32_placeholder_named(FILE *file, const char *name) +{ + struct placeholder_data *data = __placeholder_get_data(name); + if(data->offset == -1) { + stbds_arrpush(data->pending_offsets_32, ftell(file)); + w32(file, 0); + } else { + w32(file, data->offset); + } +} + +void __w16_placeholder_named(FILE *file, const char *name) +{ + struct placeholder_data *data = __placeholder_get_data(name); + if(data->offset == -1) { + stbds_arrpush(data->pending_offsets_16, ftell(file)); + w16(file, 0); + } else { + w16(file, data->offset); + } +} + +void __w8_placeholder_named(FILE *file, const char *name) +{ + struct placeholder_data *data = __placeholder_get_data(name); + if(data->offset == -1) { + stbds_arrpush(data->pending_offsets_8, ftell(file)); + w8(file, 0); + } else { + w8(file, data->offset); + } +} + +void w64_placeholdervf(FILE *file, const char *format, va_list arg) +{ + char *name = NULL; + vasprintf(&name, format, arg); + __w64_placeholder_named(file, name); + free(name); +} + +void w64_placeholderf(FILE *file, const char *format, ...) +{ + va_list args; + va_start(args, format); + w64_placeholdervf(file, format, args); + va_end(args); +} + +void w32_placeholdervf(FILE *file, const char *format, va_list arg) +{ + char *name = NULL; + vasprintf(&name, format, arg); + __w32_placeholder_named(file, name); + free(name); +} + +void w32_placeholderf(FILE *file, const char *format, ...) +{ + va_list args; + va_start(args, format); + w32_placeholdervf(file, format, args); + va_end(args); +} + +void w16_placeholdervf(FILE *file, const char *format, va_list arg) +{ + char *name = NULL; + vasprintf(&name, format, arg); + __w16_placeholder_named(file, name); + free(name); +} + +void w16_placeholderf(FILE *file, const char *format, ...) +{ + va_list args; + va_start(args, format); + w16_placeholdervf(file, format, args); + va_end(args); +} + +void w8_placeholdervf(FILE *file, const char *format, va_list arg) +{ + char *name = NULL; + vasprintf(&name, format, arg); + __w8_placeholder_named(file, name); + free(name); +} + +void w8_placeholderf(FILE *file, const char *format, ...) +{ + va_list args; + va_start(args, format); + w8_placeholdervf(file, format, args); + va_end(args); +} + +void placeholder_clear() +{ + for(int i=0; i +#include +#include +#include +#include + +#define BITCAST_F2I(f) ({ uint32_t __i; float __f = (f); memcpy(&__i, &(__f), 4); __i; }) + +#define _wconv(type, v) ({ \ + typeof(v) _v = (v); \ + if (sizeof(type) < sizeof(_v)) { \ + int64_t ext = (int64_t)_v >> (sizeof(type) * 8 - 1) >> 1; \ + if (ext != 0 && ext != -1) { \ + fprintf(stderr, "fatal: truncating value %lld to %s (ext=%lld)\n", (long long)_v, #type, (long long)ext); \ + assert(ext == 0 || ext == -1); \ + } \ + } \ + (type)_v; \ +}) + +#ifdef __cplusplus +extern "C" { +#endif + +void placeholder_setv(FILE *file, const char *format, va_list arg); +void placeholder_set(FILE *file, const char *format, ...); +void placeholder_setv_offset(FILE *file, int64_t offset, const char *format, va_list arg); +void placeholder_set_offset(FILE *file, int64_t offset, const char *format, ...); +void placeholder_clear(); + +void _w8(FILE *f, uint8_t v); +void _w16(FILE *f, uint16_t v); +void _w32(FILE *f, uint32_t v); +void _w64(FILE *f, uint64_t v); +#define w8(f, v) _w8(f, _wconv(uint8_t, v)) +#define w16(f, v) _w16(f, _wconv(uint16_t, v)) +#define w32(f, v) _w32(f, _wconv(uint32_t, v)) +#define w64(f, v) _w64(f, _wconv(uint64_t, v)) +#define wf32(f, v) _w32(f, BITCAST_F2I(v)) +#define wf32approx(f, v, prec) wf32(f, roundf((v)/(prec))*(prec)) +#define wa(f, v, s) fwrite(v, s, 1, f) + +int w64_placeholder(FILE *f); +void w64_placeholdervf(FILE *file, const char *format, va_list arg); +void w64_placeholderf(FILE *file, const char *format, ...); + +int w32_placeholder(FILE *f); +void w32_placeholdervf(FILE *file, const char *format, va_list arg); +void w32_placeholderf(FILE *file, const char *format, ...); + +int w16_placeholder(FILE *f); +void w16_placeholdervf(FILE *file, const char *format, va_list arg); +void w16_placeholderf(FILE *file, const char *format, ...); + +int w8_placeholder(FILE *f); +void w8_placeholdervf(FILE *file, const char *format, va_list arg); +void w8_placeholderf(FILE *file, const char *format, ...); + +void _w64_at(FILE *f, int pos, uint64_t v); +void _w32_at(FILE *f, int pos, uint32_t v); +void _w16_at(FILE *f, int pos, uint16_t v); +void _w8_at(FILE *f, int pos, uint8_t v); +#define w64_at(f, pos, v) _w64_at(f, pos, _wconv(uint64_t, v)) +#define w32_at(f, pos, v) _w32_at(f, pos, _wconv(uint32_t, v)) +#define w16_at(f, pos, v) _w16_at(f, pos, _wconv(uint16_t, v)) +#define w8_at(f, pos, v) _w8_at(f, pos, _wconv(uint8_t, v)) + +void walign(FILE *f, int align); +void wpad(FILE *f, int size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/common/polyfill.h b/tools/common/polyfill.h new file mode 100644 index 0000000000..543ba73cbe --- /dev/null +++ b/tools/common/polyfill.h @@ -0,0 +1,254 @@ +#ifndef LIBDRAGON_TOOLS_POLYFILL_H +#define LIBDRAGON_TOOLS_POLYFILL_H + +#ifdef __MINGW32__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// if typedef doesn't exist (msvc, blah) +typedef intptr_t ssize_t; + +/* Fetched from: https://stackoverflow.com/a/47229318 */ +/* The original code is public domain -- Will Hartung 4/9/09 */ +/* Modifications, public domain as well, by Antti Haapala, 11/10/17 + - Switched to getc on 5/23/19 */ + +ssize_t getline(char **lineptr, size_t *n, FILE *stream) { + size_t pos; + int c; + + if (lineptr == NULL || stream == NULL || n == NULL) { + errno = EINVAL; + return -1; + } + + c = getc(stream); + if (c == EOF) { + return -1; + } + + if (*lineptr == NULL) { + *lineptr = (char*)malloc(128); + if (*lineptr == NULL) { + return -1; + } + *n = 128; + } + + pos = 0; + while(c != EOF) { + if (pos + 1 >= *n) { + size_t new_size = *n + (*n >> 2); + if (new_size < 128) { + new_size = 128; + } + char *new_ptr = (char*)realloc(*lineptr, new_size); + if (new_ptr == NULL) { + return -1; + } + *n = new_size; + *lineptr = new_ptr; + } + + ((unsigned char *)(*lineptr))[pos ++] = c; + if (c == '\n') { + break; + } + c = getc(stream); + } + + (*lineptr)[pos] = '\0'; + return pos; +} + +/* This function is original code in libdragon */ +char *strndup(const char *s, size_t n) +{ + size_t len = strnlen(s, n); + char *ret = (char*)malloc(len + 1); + if (!ret) return NULL; + memcpy(ret, s, len); + ret[len] = '\0'; + return ret; +} + +// tmpfile in mingw is broken (it uses msvcrt that tries to +// create a file in C:\, which is non-writable nowadays) +#define tmpfile() mingw_tmpfile() + +typedef void* HANDLE; +typedef const char* LPCSTR; +typedef int BOOL; +#define INVALID_HANDLE_VALUE ((HANDLE)(long)-1) +struct _SECURITY_ATTRIBUTES; + +// Access rights +#define GENERIC_READ 0x80000000 +#define GENERIC_WRITE 0x40000000 + +// Share modes +#define FILE_SHARE_READ 0x00000001 +#define FILE_SHARE_WRITE 0x00000002 +#define FILE_SHARE_DELETE 0x00000004 + +// Creation disposition +#define CREATE_NEW 1 + +// Flags and attributes +#define FILE_ATTRIBUTE_TEMPORARY 0x00000100 +#define FILE_FLAG_DELETE_ON_CLOSE 0x04000000 + +__declspec(dllimport) HANDLE __stdcall CreateFileA( + LPCSTR lpFileName, + unsigned long dwDesiredAccess, + unsigned long dwShareMode, + struct _SECURITY_ATTRIBUTES* lpSecurityAttributes, + unsigned long dwCreationDisposition, + unsigned long dwFlagsAndAttributes, + HANDLE hTemplateFile +); +__declspec(dllimport) int __stdcall CloseHandle(HANDLE); +__declspec(dllimport) unsigned long __stdcall GetLastError(void); +__declspec(dllimport) unsigned long __stdcall GetTickCount(void); + +FILE *mingw_tmpfile(void) { + static int counter = 0; + char path[260]; + + for (int i = 0; i < 4096; i++) { + // Generate a random filename. Notice we *purposedly* not make this + // very random with PID, timestamp, etc. because this is the third + // iteration of mingw_tmpfile(): the previous ones were misbehaving + // in various ways on various CRTs, Windows versions, etc. + // We want this code to be exercised often so that it is robust. + snprintf(path, sizeof(path), "mksprite-%04x.tmp", counter++); + + HANDLE h = CreateFileA( + path, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + CREATE_NEW, + FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, + NULL + ); + + if (h != INVALID_HANDLE_VALUE) { + int fd = _open_osfhandle((intptr_t)h, _O_RDWR | _O_BINARY); + if (fd == -1) { + CloseHandle(h); + return NULL; + } + return fdopen(fd, "w+b"); + } + + // 80 = ERROR_FILE_EXISTS + if (GetLastError() != 80) + break; + } + + return NULL; +} +char* strcasestr(const char* haystack, const char* needle) +{ + size_t needle_len = strlen(needle); + size_t haystack_len = strlen(haystack); + size_t i; + + if (needle_len > haystack_len) + return NULL; + + for (i = 0; i <= haystack_len - needle_len; i++) + { + if (strncasecmp(haystack + i, needle, needle_len) == 0) + return (char*)(haystack + i); + } + + return NULL; +} + +// Implementation from FreeBSD +void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) +{ + char *cur, *last; + const char *cl = (const char *)l; + const char *cs = (const char *)s; + + /* we need something to compare */ + if (l_len == 0 || s_len == 0) + return NULL; + + /* "s" must be smaller or equal to "l" */ + if (l_len < s_len) + return NULL; + + /* special case where s_len == 1 */ + if (s_len == 1) + return memchr(l, (int)*cs, l_len); + + /* the last position where its possible to find "s" in "l" */ + last = (char *)cl + l_len - s_len; + + for (cur = (char *)cl; cur <= last; cur++) + if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0) + return cur; + + return NULL; +} + +// rename() that ovewrites the destination file +#define rename(a,b) mingw_rename(a,b) + +#define MOVEFILE_REPLACE_EXISTING 0x00000001 +#define MOVEFILE_WRITE_THROUGH 0x00000008 + +__declspec(dllimport) int __stdcall MoveFileExA(const char *lpExistingFileName, + const char *lpNewFileName, + unsigned long dwFlags); +__declspec(dllimport) unsigned long __stdcall GetLastError(void); + +static void map_windows_error_to_errno(unsigned long err) { + switch (err) { + case 2: errno = ENOENT; break; // ERROR_FILE_NOT_FOUND + case 3: errno = ENOENT; break; // ERROR_PATH_NOT_FOUND + case 5: errno = EACCES; break; // ERROR_ACCESS_DENIED + case 32: errno = EBUSY; break; // ERROR_SHARING_VIOLATION + case 80: errno = EEXIST; break; // ERROR_FILE_EXISTS + case 183: errno = EEXIST; break; // ERROR_ALREADY_EXISTS + default: errno = EIO; break; // Generic error + } +} + +int mingw_rename(const char *oldpath, const char *newpath) { + if (MoveFileExA(oldpath, newpath, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { + return 0; + } else { + map_windows_error_to_errno(GetLastError()); + return -1; + } +} + +// POISX mkdir has a mode argument, but mingw's mkdir doesn't +#define mkdir(path, mode) mkdir(path) + +#ifdef __cplusplus +} +#endif + + +#endif + +#endif diff --git a/tools/common/stb_ds.h b/tools/common/stb_ds.h new file mode 100644 index 0000000000..e84c82d1d5 --- /dev/null +++ b/tools/common/stb_ds.h @@ -0,0 +1,1895 @@ +/* stb_ds.h - v0.67 - public domain data structures - Sean Barrett 2019 + + This is a single-header-file library that provides easy-to-use + dynamic arrays and hash tables for C (also works in C++). + + For a gentle introduction: + http://nothings.org/stb_ds + + To use this library, do this in *one* C or C++ file: + #define STB_DS_IMPLEMENTATION + #include "stb_ds.h" + +TABLE OF CONTENTS + + Table of Contents + Compile-time options + License + Documentation + Notes + Notes - Dynamic arrays + Notes - Hash maps + Credits + +COMPILE-TIME OPTIONS + + #define STBDS_NO_SHORT_NAMES + + This flag needs to be set globally. + + By default stb_ds exposes shorter function names that are not qualified + with the "stbds_" prefix. If these names conflict with the names in your + code, define this flag. + + #define STBDS_SIPHASH_2_4 + + This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for + 4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force + stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes + hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on + 64-byte keys, and 10% slower on 256-byte keys on my test computer. + + #define STBDS_REALLOC(context,ptr,size) better_realloc + #define STBDS_FREE(context,ptr) better_free + + These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds uses stdlib realloc() and free() for memory management. You can + substitute your own functions instead by defining these symbols. You must either + define both, or neither. Note that at the moment, 'context' will always be NULL. + @TODO add an array/hash initialization function that takes a memory context pointer. + + #define STBDS_UNIT_TESTS + + Defines a function stbds_unit_tests() that checks the functioning of the data structures. + + Note that on older versions of gcc (e.g. 5.x.x) you may need to build with '-std=c++0x' + (or equivalentally '-std=c++11') when using anonymous structures as seen on the web + page or in STBDS_UNIT_TESTS. + +LICENSE + + Placed in the public domain and also MIT licensed. + See end of file for detailed license information. + +DOCUMENTATION + + Dynamic Arrays + + Non-function interface: + + Declare an empty dynamic array of type T + T* foo = NULL; + + Access the i'th item of a dynamic array 'foo' of type T, T* foo: + foo[i] + + Functions (actually macros) + + arrfree: + void arrfree(T*); + Frees the array. + + arrlen: + ptrdiff_t arrlen(T*); + Returns the number of elements in the array. + + arrlenu: + size_t arrlenu(T*); + Returns the number of elements in the array as an unsigned type. + + arrpop: + T arrpop(T* a) + Removes the final element of the array and returns it. + + arrput: + T arrput(T* a, T b); + Appends the item b to the end of array a. Returns b. + + arrins: + T arrins(T* a, int p, T b); + Inserts the item b into the middle of array a, into a[p], + moving the rest of the array over. Returns b. + + arrinsn: + void arrinsn(T* a, int p, int n); + Inserts n uninitialized items into array a starting at a[p], + moving the rest of the array over. + + arraddnptr: + T* arraddnptr(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns a pointer to the first uninitialized item added. + + arraddnindex: + size_t arraddnindex(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns the index of the first uninitialized item added. + + arrdel: + void arrdel(T* a, int p); + Deletes the element at a[p], moving the rest of the array over. + + arrdeln: + void arrdeln(T* a, int p, int n); + Deletes n elements starting at a[p], moving the rest of the array over. + + arrdelswap: + void arrdelswap(T* a, int p); + Deletes the element at a[p], replacing it with the element from + the end of the array. O(1) performance. + + arrsetlen: + void arrsetlen(T* a, int n); + Changes the length of the array to n. Allocates uninitialized + slots at the end if necessary. + + arrsetcap: + size_t arrsetcap(T* a, int n); + Sets the length of allocated storage to at least n. It will not + change the length of the array. + + arrcap: + size_t arrcap(T* a); + Returns the number of total elements the array can contain without + needing to be reallocated. + + Hash maps & String hash maps + + Given T is a structure type: struct { TK key; TV value; }. Note that some + functions do not require TV value and can have other fields. For string + hash maps, TK must be 'char *'. + + Special interface: + + stbds_rand_seed: + void stbds_rand_seed(size_t seed); + For security against adversarially chosen data, you should seed the + library with a strong random number. Or at least seed it with time(). + + stbds_hash_string: + size_t stbds_hash_string(char *str, size_t seed); + Returns a hash value for a string. + + stbds_hash_bytes: + size_t stbds_hash_bytes(void *p, size_t len, size_t seed); + These functions hash an arbitrary number of bytes. The function + uses a custom hash for 4- and 8-byte data, and a weakened version + of SipHash for everything else. On 64-bit platforms you can get + specification-compliant SipHash-2-4 on all data by defining + STBDS_SIPHASH_2_4, at a significant cost in speed. + + Non-function interface: + + Declare an empty hash map of type T + T* foo = NULL; + + Access the i'th entry in a hash table T* foo: + foo[i] + + Function interface (actually macros): + + hmfree + shfree + void hmfree(T*); + void shfree(T*); + Frees the hashmap and sets the pointer to NULL. + + hmlen + shlen + ptrdiff_t hmlen(T*) + ptrdiff_t shlen(T*) + Returns the number of elements in the hashmap. + + hmlenu + shlenu + size_t hmlenu(T*) + size_t shlenu(T*) + Returns the number of elements in the hashmap. + + hmgeti + shgeti + hmgeti_ts + ptrdiff_t hmgeti(T*, TK key) + ptrdiff_t shgeti(T*, char* key) + ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar) + Returns the index in the hashmap which has the key 'key', or -1 + if the key is not present. + + hmget + hmget_ts + shget + TV hmget(T*, TK key) + TV shget(T*, char* key) + TV hmget_ts(T*, TK key, ptrdiff_t tempvar) + Returns the value corresponding to 'key' in the hashmap. + The structure must have a 'value' field + + hmgets + shgets + T hmgets(T*, TK key) + T shgets(T*, char* key) + Returns the structure corresponding to 'key' in the hashmap. + + hmgetp + shgetp + hmgetp_ts + hmgetp_null + shgetp_null + T* hmgetp(T*, TK key) + T* shgetp(T*, char* key) + T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar) + T* hmgetp_null(T*, TK key) + T* shgetp_null(T*, char *key) + Returns a pointer to the structure corresponding to 'key' in + the hashmap. Functions ending in "_null" return NULL if the key + is not present in the hashmap; the others return a pointer to a + structure holding the default value (but not the searched-for key). + + hmdefault + shdefault + TV hmdefault(T*, TV value) + TV shdefault(T*, TV value) + Sets the default value for the hashmap, the value which will be + returned by hmget/shget if the key is not present. + + hmdefaults + shdefaults + TV hmdefaults(T*, T item) + TV shdefaults(T*, T item) + Sets the default struct for the hashmap, the contents which will be + returned by hmgets/shgets if the key is not present. + + hmput + shput + TV hmput(T*, TK key, TV value) + TV shput(T*, char* key, TV value) + Inserts a pair into the hashmap. If the key is already + present in the hashmap, updates its value. + + hmputs + shputs + T hmputs(T*, T item) + T shputs(T*, T item) + Inserts a struct with T.key into the hashmap. If the struct is already + present in the hashmap, updates it. + + hmdel + shdel + int hmdel(T*, TK key) + int shdel(T*, char* key) + If 'key' is in the hashmap, deletes its entry and returns 1. + Otherwise returns 0. + + Function interface (actually macros) for strings only: + + sh_new_strdup + void sh_new_strdup(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate and free + each string key using realloc/free + + sh_new_arena + void sh_new_arena(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate each string + key to a string arena. Every string key ever used by this + hash table remains in the arena until the arena is freed. + Additionally, any key which is deleted and reinserted will + be allocated multiple times in the string arena. + +NOTES + + * These data structures are realloc'd when they grow, and the macro + "functions" write to the provided pointer. This means: (a) the pointer + must be an lvalue, and (b) the pointer to the data structure is not + stable, and you must maintain it the same as you would a realloc'd + pointer. For example, if you pass a pointer to a dynamic array to a + function which updates it, the function must return back the new + pointer to the caller. This is the price of trying to do this in C. + + * The following are the only functions that are thread-safe on a single data + structure, i.e. can be run in multiple threads simultaneously on the same + data structure + hmlen shlen + hmlenu shlenu + hmget_ts shget_ts + hmgeti_ts shgeti_ts + hmgets_ts shgets_ts + + * You iterate over the contents of a dynamic array and a hashmap in exactly + the same way, using arrlen/hmlen/shlen: + + for (i=0; i < arrlen(foo); ++i) + ... foo[i] ... + + * All operations except arrins/arrdel are O(1) amortized, but individual + operations can be slow, so these data structures may not be suitable + for real time use. Dynamic arrays double in capacity as needed, so + elements are copied an average of once. Hash tables double/halve + their size as needed, with appropriate hysteresis to maintain O(1) + performance. + +NOTES - DYNAMIC ARRAY + + * If you know how long a dynamic array is going to be in advance, you can avoid + extra memory allocations by using arrsetlen to allocate it to that length in + advance and use foo[n] while filling it out, or arrsetcap to allocate the memory + for that length and use arrput/arrpush as normal. + + * Unlike some other versions of the dynamic array, this version should + be safe to use with strict-aliasing optimizations. + +NOTES - HASH MAP + + * For compilers other than GCC and clang (e.g. Visual Studio), for hmput/hmget/hmdel + and variants, the key must be an lvalue (so the macro can take the address of it). + Extensions are used that eliminate this requirement if you're using C99 and later + in GCC or clang, or if you're using C++ in GCC. But note that this can make your + code less portable. + + * To test for presence of a key in a hashmap, just do 'hmgeti(foo,key) >= 0'. + + * The iteration order of your data in the hashmap is determined solely by the + order of insertions and deletions. In particular, if you never delete, new + keys are always added at the end of the array. This will be consistent + across all platforms and versions of the library. However, you should not + attempt to serialize the internal hash table, as the hash is not consistent + between different platforms, and may change with future versions of the library. + + * Use sh_new_arena() for string hashmaps that you never delete from. Initialize + with NULL if you're managing the memory for your strings, or your strings are + never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup(). + @TODO: make an arena variant that garbage collects the strings with a trivial + copy collector into a new arena whenever the table shrinks / rebuilds. Since + current arena recommendation is to only use arena if it never deletes, then + this can just replace current arena implementation. + + * If adversarial input is a serious concern and you're on a 64-bit platform, + enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass + a strong random number to stbds_rand_seed. + + * The default value for the hash table is stored in foo[-1], so if you + use code like 'hmget(T,k)->value = 5' you can accidentally overwrite + the value stored by hmdefault if 'k' is not present. + +CREDITS + + Sean Barrett -- library, idea for dynamic array API/implementation + Per Vognsen -- idea for hash table API/implementation + Rafael Sachetto -- arrpop() + github:HeroicKatora -- arraddn() reworking + + Bugfixes: + Andy Durdin + Shane Liesegang + Vinh Truong + Andreas Molzer + github:hashitaku + github:srdjanstipic + Macoy Madson + Andreas Vennstrom + Tobias Mansfield-Williams +*/ + +#ifdef STBDS_UNIT_TESTS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifndef INCLUDE_STB_DS_H +#define INCLUDE_STB_DS_H + +#include +#include + +#ifndef STBDS_NO_SHORT_NAMES +#define arrlen stbds_arrlen +#define arrlenu stbds_arrlenu +#define arrput stbds_arrput +#define arrpush stbds_arrput +#define arrpop stbds_arrpop +#define arrfree stbds_arrfree +#define arraddn stbds_arraddn // deprecated, use one of the following instead: +#define arraddnptr stbds_arraddnptr +#define arraddnindex stbds_arraddnindex +#define arrsetlen stbds_arrsetlen +#define arrlast stbds_arrlast +#define arrins stbds_arrins +#define arrinsn stbds_arrinsn +#define arrdel stbds_arrdel +#define arrdeln stbds_arrdeln +#define arrdelswap stbds_arrdelswap +#define arrcap stbds_arrcap +#define arrsetcap stbds_arrsetcap + +#define hmput stbds_hmput +#define hmputs stbds_hmputs +#define hmget stbds_hmget +#define hmget_ts stbds_hmget_ts +#define hmgets stbds_hmgets +#define hmgetp stbds_hmgetp +#define hmgetp_ts stbds_hmgetp_ts +#define hmgetp_null stbds_hmgetp_null +#define hmgeti stbds_hmgeti +#define hmgeti_ts stbds_hmgeti_ts +#define hmdel stbds_hmdel +#define hmlen stbds_hmlen +#define hmlenu stbds_hmlenu +#define hmfree stbds_hmfree +#define hmdefault stbds_hmdefault +#define hmdefaults stbds_hmdefaults + +#define shput stbds_shput +#define shputi stbds_shputi +#define shputs stbds_shputs +#define shget stbds_shget +#define shgeti stbds_shgeti +#define shgets stbds_shgets +#define shgetp stbds_shgetp +#define shgetp_null stbds_shgetp_null +#define shdel stbds_shdel +#define shlen stbds_shlen +#define shlenu stbds_shlenu +#define shfree stbds_shfree +#define shdefault stbds_shdefault +#define shdefaults stbds_shdefaults +#define sh_new_arena stbds_sh_new_arena +#define sh_new_strdup stbds_sh_new_strdup + +#define stralloc stbds_stralloc +#define strreset stbds_strreset +#endif + +#if defined(STBDS_REALLOC) && !defined(STBDS_FREE) || !defined(STBDS_REALLOC) && defined(STBDS_FREE) +#error "You must define both STBDS_REALLOC and STBDS_FREE, or neither." +#endif +#if !defined(STBDS_REALLOC) && !defined(STBDS_FREE) +#include +#define STBDS_REALLOC(c,p,s) realloc(p,s) +#define STBDS_FREE(c,p) free(p) +#endif + +#ifdef _MSC_VER +#define STBDS_NOTUSED(v) (void)(v) +#else +#define STBDS_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// for security against attackers, seed the library with a random number, at least time() but stronger is better +extern void stbds_rand_seed(size_t seed); + +// these are the hash functions used internally if you want to test them or use them for other purposes +extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed); +extern size_t stbds_hash_string(char *str, size_t seed); + +// this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'. +typedef struct stbds_string_arena stbds_string_arena; +extern char * stbds_stralloc(stbds_string_arena *a, char *str); +extern void stbds_strreset(stbds_string_arena *a); + +// have to #define STBDS_UNIT_TESTS to call this +extern void stbds_unit_tests(void); + +/////////////// +// +// Everything below here is implementation details +// + +extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap); +extern void stbds_arrfreef(void *a); +extern void stbds_hmfree_func(void *p, size_t elemsize); +extern void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode); +extern void * stbds_hmput_default(void *a, size_t elemsize); +extern void * stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode); +extern void * stbds_shmode_func(size_t elemsize, int mode); + +#ifdef __cplusplus +} +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define STBDS_HAS_TYPEOF +#ifdef __cplusplus +//#define STBDS_HAS_LITERAL_ARRAY // this is currently broken for clang +#endif +#endif + +#if !defined(__cplusplus) +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define STBDS_HAS_LITERAL_ARRAY +#endif +#endif + +// this macro takes the address of the argument, but on gcc/clang can accept rvalues +#if defined(STBDS_HAS_LITERAL_ARRAY) && defined(STBDS_HAS_TYPEOF) + #if __clang__ + #define STBDS_ADDRESSOF(typevar, value) ((__typeof__(typevar)[1]){value}) // literal array decays to pointer to value + #else + #define STBDS_ADDRESSOF(typevar, value) ((typeof(typevar)[1]){value}) // literal array decays to pointer to value + #endif +#else +#define STBDS_ADDRESSOF(typevar, value) &(value) +#endif + +#define STBDS_OFFSETOF(var,field) ((char *) &(var)->field - (char *) (var)) + +#define stbds_header(t) ((stbds_array_header *) (t) - 1) +#define stbds_temp(t) stbds_header(t)->temp +#define stbds_temp_key(t) (*(char **) stbds_header(t)->hash_table) + +#define stbds_arrsetcap(a,n) (stbds_arrgrow(a,0,n)) +#define stbds_arrsetlen(a,n) ((stbds_arrcap(a) < (size_t) (n) ? stbds_arrsetcap((a),(size_t)(n)),0 : 0), (a) ? stbds_header(a)->length = (size_t) (n) : 0) +#define stbds_arrcap(a) ((a) ? stbds_header(a)->capacity : 0) +#define stbds_arrlen(a) ((a) ? (ptrdiff_t) stbds_header(a)->length : 0) +#define stbds_arrlenu(a) ((a) ? stbds_header(a)->length : 0) +#define stbds_arrput(a,v) (stbds_arrmaybegrow(a,1), (a)[stbds_header(a)->length++] = (v)) +#define stbds_arrpush stbds_arrput // synonym +#define stbds_arrpop(a) (stbds_header(a)->length--, (a)[stbds_header(a)->length]) +#define stbds_arraddn(a,n) ((void)(stbds_arraddnindex(a, n))) // deprecated, use one of the following instead: +#define stbds_arraddnptr(a,n) (stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), &(a)[stbds_header(a)->length-(n)]) : (a)) +#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a)) +#define stbds_arraddnoff stbds_arraddnindex +#define stbds_arrlast(a) ((a)[stbds_header(a)->length-1]) +#define stbds_arrfree(a) ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL) +#define stbds_arrdel(a,i) stbds_arrdeln(a,i,1) +#define stbds_arrdeln(a,i,n) (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n)) +#define stbds_arrdelswap(a,i) ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1) +#define stbds_arrinsn(a,i,n) (stbds_arraddn((a),(n)), memmove(&(a)[(i)+(n)], &(a)[i], sizeof *(a) * (stbds_header(a)->length-(n)-(i)))) +#define stbds_arrins(a,i,v) (stbds_arrinsn((a),(i),1), (a)[i]=(v)) + +#define stbds_arrmaybegrow(a,n) ((!(a) || stbds_header(a)->length + (n) > stbds_header(a)->capacity) \ + ? (stbds_arrgrow(a,n,0),0) : 0) + +#define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c))) + +#define stbds_hmput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \ + (t)[stbds_temp((t)-1)].key = (k), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_hmputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \ + (t)[stbds_temp((t)-1)] = (s)) + +#define stbds_hmgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \ + stbds_temp((t)-1)) + +#define stbds_hmgeti_ts(t,k,temp) \ + ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \ + (temp)) + +#define stbds_hmgetp(t, k) \ + ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_hmgetp_ts(t, k, temp) \ + ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp]) + +#define stbds_hmdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0) + +#define stbds_hmdefault(t, v) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v)) + +#define stbds_hmdefaults(t, s) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s)) + +#define stbds_hmfree(p) \ + ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL) + +#define stbds_hmgets(t, k) (*stbds_hmgetp(t,k)) +#define stbds_hmget(t, k) (stbds_hmgetp(t,k)->value) +#define stbds_hmget_ts(t, k, temp) (stbds_hmgetp_ts(t,k,temp)->value) +#define stbds_hmlen(t) ((t) ? (ptrdiff_t) stbds_header((t)-1)->length-1 : 0) +#define stbds_hmlenu(t) ((t) ? stbds_header((t)-1)->length-1 : 0) +#define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) + +#define stbds_shput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_shputi(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1)) + +#define stbds_shputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)] = (s), \ + (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally + +#define stbds_pshput(t, p) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \ + (t)[stbds_temp((t)-1)] = (p)) + +#define stbds_shgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + stbds_temp((t)-1)) + +#define stbds_pshgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \ + stbds_temp((t)-1)) + +#define stbds_shgetp(t, k) \ + ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_pshget(t, k) \ + ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)]) + +#define stbds_shdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0) +#define stbds_pshdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0) + +#define stbds_sh_new_arena(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA)) +#define stbds_sh_new_strdup(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP)) + +#define stbds_shdefault(t, v) stbds_hmdefault(t,v) +#define stbds_shdefaults(t, s) stbds_hmdefaults(t,s) + +#define stbds_shfree stbds_hmfree +#define stbds_shlenu stbds_hmlenu + +#define stbds_shgets(t, k) (*stbds_shgetp(t,k)) +#define stbds_shget(t, k) (stbds_shgetp(t,k)->value) +#define stbds_shgetp_null(t,k) (stbds_shgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) +#define stbds_shlen stbds_hmlen + +typedef struct +{ + size_t length; + size_t capacity; + void * hash_table; + ptrdiff_t temp; +} stbds_array_header; + +typedef struct stbds_string_block +{ + struct stbds_string_block *next; + char storage[8]; +} stbds_string_block; + +struct stbds_string_arena +{ + stbds_string_block *storage; + size_t remaining; + unsigned char block; + unsigned char mode; // this isn't used by the string arena itself +}; + +#define STBDS_HM_BINARY 0 +#define STBDS_HM_STRING 1 + +enum +{ + STBDS_SH_NONE, + STBDS_SH_DEFAULT, + STBDS_SH_STRDUP, + STBDS_SH_ARENA +}; + +#ifdef __cplusplus +// in C we use implicit assignment from these void*-returning functions to T*. +// in C++ these templates make the same code work +template static T * stbds_arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) { + return (T*)stbds_arrgrowf((void *)a, elemsize, addlen, min_cap); +} +template static T * stbds_hmget_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmget_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmget_key_ts_wrapper(T *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) { + return (T*)stbds_hmget_key_ts((void*)a, elemsize, key, keysize, temp, mode); +} +template static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) { + return (T*)stbds_hmput_default((void *)a, elemsize); +} +template static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmdel_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode){ + return (T*)stbds_hmdel_key((void*)a, elemsize, key, keysize, keyoffset, mode); +} +template static T * stbds_shmode_func_wrapper(T *, size_t elemsize, int mode) { + return (T*)stbds_shmode_func(elemsize, mode); +} +#else +#define stbds_arrgrowf_wrapper stbds_arrgrowf +#define stbds_hmget_key_wrapper stbds_hmget_key +#define stbds_hmget_key_ts_wrapper stbds_hmget_key_ts +#define stbds_hmput_default_wrapper stbds_hmput_default +#define stbds_hmput_key_wrapper stbds_hmput_key +#define stbds_hmdel_key_wrapper stbds_hmdel_key +#define stbds_shmode_func_wrapper(t,e,m) stbds_shmode_func(e,m) +#endif + +#endif // INCLUDE_STB_DS_H + + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// + +#ifdef STB_DS_IMPLEMENTATION +#include +#include + +#ifndef STBDS_ASSERT +#define STBDS_ASSERT_WAS_UNDEFINED +#define STBDS_ASSERT(x) ((void) 0) +#endif + +#ifdef STBDS_STATISTICS +#define STBDS_STATS(x) x +size_t stbds_array_grow; +size_t stbds_hash_grow; +size_t stbds_hash_shrink; +size_t stbds_hash_rebuild; +size_t stbds_hash_probes; +size_t stbds_hash_alloc; +size_t stbds_rehash_probes; +size_t stbds_rehash_items; +#else +#define STBDS_STATS(x) +#endif + +// +// stbds_arr implementation +// + +//int *prev_allocs[65536]; +//int num_prev; + +void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap) +{ + stbds_array_header temp={0}; // force debugging + void *b; + size_t min_len = stbds_arrlen(a) + addlen; + (void) sizeof(temp); + + // compute the minimum capacity needed + if (min_len > min_cap) + min_cap = min_len; + + if (min_cap <= stbds_arrcap(a)) + return a; + + // increase needed capacity to guarantee O(1) amortized + if (min_cap < 2 * stbds_arrcap(a)) + min_cap = 2 * stbds_arrcap(a); + else if (min_cap < 4) + min_cap = 4; + + //if (num_prev < 65536) if (a) prev_allocs[num_prev++] = (int *) ((char *) a+1); + //if (num_prev == 2201) + // num_prev = num_prev; + b = STBDS_REALLOC(NULL, (a) ? stbds_header(a) : 0, elemsize * min_cap + sizeof(stbds_array_header)); + //if (num_prev < 65536) prev_allocs[num_prev++] = (int *) (char *) b; + b = (char *) b + sizeof(stbds_array_header); + if (a == NULL) { + stbds_header(b)->length = 0; + stbds_header(b)->hash_table = 0; + stbds_header(b)->temp = 0; + } else { + STBDS_STATS(++stbds_array_grow); + } + stbds_header(b)->capacity = min_cap; + + return b; +} + +void stbds_arrfreef(void *a) +{ + STBDS_FREE(NULL, stbds_header(a)); +} + +// +// stbds_hm hash table implementation +// + +#ifdef STBDS_INTERNAL_SMALL_BUCKET +#define STBDS_BUCKET_LENGTH 4 +#else +#define STBDS_BUCKET_LENGTH 8 +#endif + +#define STBDS_BUCKET_SHIFT (STBDS_BUCKET_LENGTH == 8 ? 3 : 2) +#define STBDS_BUCKET_MASK (STBDS_BUCKET_LENGTH-1) +#define STBDS_CACHE_LINE_SIZE 64 + +#define STBDS_ALIGN_FWD(n,a) (((n) + (a) - 1) & ~((a)-1)) + +typedef struct +{ + size_t hash [STBDS_BUCKET_LENGTH]; + ptrdiff_t index[STBDS_BUCKET_LENGTH]; +} stbds_hash_bucket; // in 32-bit, this is one 64-byte cache line; in 64-bit, each array is one 64-byte cache line + +typedef struct +{ + char * temp_key; // this MUST be the first field of the hash table + size_t slot_count; + size_t used_count; + size_t used_count_threshold; + size_t used_count_shrink_threshold; + size_t tombstone_count; + size_t tombstone_count_threshold; + size_t seed; + size_t slot_count_log2; + stbds_string_arena string; + stbds_hash_bucket *storage; // not a separate allocation, just 64-byte aligned storage after this struct +} stbds_hash_index; + +#define STBDS_INDEX_EMPTY -1 +#define STBDS_INDEX_DELETED -2 +#define STBDS_INDEX_IN_USE(x) ((x) >= 0) + +#define STBDS_HASH_EMPTY 0 +#define STBDS_HASH_DELETED 1 + +static size_t stbds_hash_seed=0x31415926; + +void stbds_rand_seed(size_t seed) +{ + stbds_hash_seed = seed; +} + +#define stbds_load_32_or_64(var, temp, v32, v64_hi, v64_lo) \ + temp = v64_lo ^ v32, temp <<= 16, temp <<= 16, temp >>= 16, temp >>= 16, /* discard if 32-bit */ \ + var = v64_hi, var <<= 16, var <<= 16, /* discard if 32-bit */ \ + var ^= temp ^ v32 + +#define STBDS_SIZE_T_BITS ((sizeof (size_t)) * 8) + +static size_t stbds_probe_position(size_t hash, size_t slot_count, size_t slot_log2) +{ + size_t pos; + STBDS_NOTUSED(slot_log2); + pos = hash & (slot_count-1); + #ifdef STBDS_INTERNAL_BUCKET_START + pos &= ~STBDS_BUCKET_MASK; + #endif + return pos; +} + +static size_t stbds_log2(size_t slot_count) +{ + size_t n=0; + while (slot_count > 1) { + slot_count >>= 1; + ++n; + } + return n; +} + +static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_index *ot) +{ + stbds_hash_index *t; + t = (stbds_hash_index *) STBDS_REALLOC(NULL,0,(slot_count >> STBDS_BUCKET_SHIFT) * sizeof(stbds_hash_bucket) + sizeof(stbds_hash_index) + STBDS_CACHE_LINE_SIZE-1); + t->storage = (stbds_hash_bucket *) STBDS_ALIGN_FWD((size_t) (t+1), STBDS_CACHE_LINE_SIZE); + t->slot_count = slot_count; + t->slot_count_log2 = stbds_log2(slot_count); + t->tombstone_count = 0; + t->used_count = 0; + + #if 0 // A1 + t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + #elif 1 // A2 + //t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + //t->tombstone_count_threshold = slot_count* 3/16; // if tombstones are 3/16th of table, rebuild + //t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + + // compute without overflowing + t->used_count_threshold = slot_count - (slot_count>>2); + t->tombstone_count_threshold = (slot_count>>3) + (slot_count>>4); + t->used_count_shrink_threshold = slot_count >> 2; + + #elif 0 // B1 + t->used_count_threshold = slot_count*13/16; // if 13/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 5/16; // if table is only 5/16th full, shrink + #else // C1 + t->used_count_threshold = slot_count*14/16; // if 14/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 6/16; // if table is only 6/16th full, shrink + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // A1 A2 B1 C1 + // 0.10ms : 0.10ms : 0.10ms : 0.11ms : 2,000 inserts creating 2K table + // 0.96ms : 0.95ms : 0.97ms : 1.04ms : 20,000 inserts creating 20K table + // 14.48ms : 14.46ms : 10.63ms : 11.00ms : 200,000 inserts creating 200K table + // 195.74ms : 196.35ms : 203.69ms : 214.92ms : 2,000,000 inserts creating 2M table + // 2193.88ms : 2209.22ms : 2285.54ms : 2437.17ms : 20,000,000 inserts creating 20M table + // 65.27ms : 53.77ms : 65.33ms : 65.47ms : 500,000 inserts & deletes in 2K table + // 72.78ms : 62.45ms : 71.95ms : 72.85ms : 500,000 inserts & deletes in 20K table + // 89.47ms : 77.72ms : 96.49ms : 96.75ms : 500,000 inserts & deletes in 200K table + // 97.58ms : 98.14ms : 97.18ms : 97.53ms : 500,000 inserts & deletes in 2M table + // 118.61ms : 119.62ms : 120.16ms : 118.86ms : 500,000 inserts & deletes in 20M table + // 192.11ms : 194.39ms : 196.38ms : 195.73ms : 500,000 inserts & deletes in 200M table + + if (slot_count <= STBDS_BUCKET_LENGTH) + t->used_count_shrink_threshold = 0; + // to avoid infinite loop, we need to guarantee that at least one slot is empty and will terminate probes + STBDS_ASSERT(t->used_count_threshold + t->tombstone_count_threshold < t->slot_count); + STBDS_STATS(++stbds_hash_alloc); + if (ot) { + t->string = ot->string; + // reuse old seed so we can reuse old hashes so below "copy out old data" doesn't do any hashing + t->seed = ot->seed; + } else { + size_t a,b,temp; + memset(&t->string, 0, sizeof(t->string)); + t->seed = stbds_hash_seed; + // LCG + // in 32-bit, a = 2147001325 b = 715136305 + // in 64-bit, a = 2862933555777941757 b = 3037000493 + stbds_load_32_or_64(a,temp, 2147001325, 0x27bb2ee6, 0x87b0b0fd); + stbds_load_32_or_64(b,temp, 715136305, 0, 0xb504f32d); + stbds_hash_seed = stbds_hash_seed * a + b; + } + + { + size_t i,j; + for (i=0; i < slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *b = &t->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->hash[j] = STBDS_HASH_EMPTY; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->index[j] = STBDS_INDEX_EMPTY; + } + } + + // copy out the old data, if any + if (ot) { + size_t i,j; + t->used_count = ot->used_count; + for (i=0; i < ot->slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *ob = &ot->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) { + if (STBDS_INDEX_IN_USE(ob->index[j])) { + size_t hash = ob->hash[j]; + size_t pos = stbds_probe_position(hash, t->slot_count, t->slot_count_log2); + size_t step = STBDS_BUCKET_LENGTH; + STBDS_STATS(++stbds_rehash_items); + for (;;) { + size_t limit,z; + stbds_hash_bucket *bucket; + bucket = &t->storage[pos >> STBDS_BUCKET_SHIFT]; + STBDS_STATS(++stbds_rehash_probes); + + for (z=pos & STBDS_BUCKET_MASK; z < STBDS_BUCKET_LENGTH; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + limit = pos & STBDS_BUCKET_MASK; + for (z = 0; z < limit; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + pos += step; // quadratic probing + step += STBDS_BUCKET_LENGTH; + pos &= (t->slot_count-1); + } + } + done: + ; + } + } + } + + return t; +} + +#define STBDS_ROTATE_LEFT(val, n) (((val) << (n)) | ((val) >> (STBDS_SIZE_T_BITS - (n)))) +#define STBDS_ROTATE_RIGHT(val, n) (((val) >> (n)) | ((val) << (STBDS_SIZE_T_BITS - (n)))) + +size_t stbds_hash_string(char *str, size_t seed) +{ + size_t hash = seed; + while (*str) + hash = STBDS_ROTATE_LEFT(hash, 9) + (unsigned char) *str++; + + // Thomas Wang 64-to-32 bit mix function, hopefully also works in 32 bits + hash ^= seed; + hash = (~hash) + (hash << 18); + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,31); + hash = hash * 21; + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,11); + hash += (hash << 6); + hash ^= STBDS_ROTATE_RIGHT(hash,22); + return hash+seed; +} + +#ifdef STBDS_SIPHASH_2_4 +#define STBDS_SIPHASH_C_ROUNDS 2 +#define STBDS_SIPHASH_D_ROUNDS 4 +typedef int STBDS_SIPHASH_2_4_can_only_be_used_in_64_bit_builds[sizeof(size_t) == 8 ? 1 : -1]; +#endif + +#ifndef STBDS_SIPHASH_C_ROUNDS +#define STBDS_SIPHASH_C_ROUNDS 1 +#endif +#ifndef STBDS_SIPHASH_D_ROUNDS +#define STBDS_SIPHASH_D_ROUNDS 1 +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4127) // conditional expression is constant, for do..while(0) and sizeof()== +#endif + +static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed) +{ + unsigned char *d = (unsigned char *) p; + size_t i,j; + size_t v0,v1,v2,v3, data; + + // hash that works on 32- or 64-bit registers without knowing which we have + // (computes different results on 32-bit and 64-bit platform) + // derived from siphash, but on 32-bit platforms very different as it uses 4 32-bit state not 4 64-bit + v0 = ((((size_t) 0x736f6d65 << 16) << 16) + 0x70736575) ^ seed; + v1 = ((((size_t) 0x646f7261 << 16) << 16) + 0x6e646f6d) ^ ~seed; + v2 = ((((size_t) 0x6c796765 << 16) << 16) + 0x6e657261) ^ seed; + v3 = ((((size_t) 0x74656462 << 16) << 16) + 0x79746573) ^ ~seed; + + #ifdef STBDS_TEST_SIPHASH_2_4 + // hardcoded with key material in the siphash test vectors + v0 ^= 0x0706050403020100ull ^ seed; + v1 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + v2 ^= 0x0706050403020100ull ^ seed; + v3 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + #endif + + #define STBDS_SIPROUND() \ + do { \ + v0 += v1; v1 = STBDS_ROTATE_LEFT(v1, 13); v1 ^= v0; v0 = STBDS_ROTATE_LEFT(v0,STBDS_SIZE_T_BITS/2); \ + v2 += v3; v3 = STBDS_ROTATE_LEFT(v3, 16); v3 ^= v2; \ + v2 += v1; v1 = STBDS_ROTATE_LEFT(v1, 17); v1 ^= v2; v2 = STBDS_ROTATE_LEFT(v2,STBDS_SIZE_T_BITS/2); \ + v0 += v3; v3 = STBDS_ROTATE_LEFT(v3, 21); v3 ^= v0; \ + } while (0) + + for (i=0; i+sizeof(size_t) <= len; i += sizeof(size_t), d += sizeof(size_t)) { + data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4 + + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + } + data = len << (STBDS_SIZE_T_BITS-8); + switch (len - i) { + case 7: data |= ((size_t) d[6] << 24) << 24; // fall through + case 6: data |= ((size_t) d[5] << 20) << 20; // fall through + case 5: data |= ((size_t) d[4] << 16) << 16; // fall through + case 4: data |= (d[3] << 24); // fall through + case 3: data |= (d[2] << 16); // fall through + case 2: data |= (d[1] << 8); // fall through + case 1: data |= d[0]; // fall through + case 0: break; + } + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + v2 ^= 0xff; + for (j=0; j < STBDS_SIPHASH_D_ROUNDS; ++j) + STBDS_SIPROUND(); + +#ifdef STBDS_SIPHASH_2_4 + return v0^v1^v2^v3; +#else + return v1^v2^v3; // slightly stronger since v0^v3 in above cancels out final round operation? I tweeted at the authors of SipHash about this but they didn't reply +#endif +} + +size_t stbds_hash_bytes(void *p, size_t len, size_t seed) +{ +#ifdef STBDS_SIPHASH_2_4 + return stbds_siphash_bytes(p,len,seed); +#else + unsigned char *d = (unsigned char *) p; + + if (len == 4) { + unsigned int hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + #if 0 + // HASH32-A Bob Jenkin's hash function w/o large constants + hash ^= seed; + hash -= (hash<<6); + hash ^= (hash>>17); + hash -= (hash<<9); + hash ^= seed; + hash ^= (hash<<4); + hash -= (hash<<3); + hash ^= (hash<<10); + hash ^= (hash>>15); + #elif 1 + // HASH32-BB Bob Jenkin's presumably-accidental version of Thomas Wang hash with rotates turned into shifts. + // Note that converting these back to rotates makes it run a lot slower, presumably due to collisions, so I'm + // not really sure what's going on. + hash ^= seed; + hash = (hash ^ 61) ^ (hash >> 16); + hash = hash + (hash << 3); + hash = hash ^ (hash >> 4); + hash = hash * 0x27d4eb2d; + hash ^= seed; + hash = hash ^ (hash >> 15); + #else // HASH32-C - Murmur3 + hash ^= seed; + hash *= 0xcc9e2d51; + hash = (hash << 17) | (hash >> 15); + hash *= 0x1b873593; + hash ^= seed; + hash = (hash << 19) | (hash >> 13); + hash = hash*5 + 0xe6546b64; + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= seed; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // HASH32-A // HASH32-BB // HASH32-C + // 0.10ms // 0.10ms // 0.10ms : 2,000 inserts creating 2K table + // 0.96ms // 0.95ms // 0.99ms : 20,000 inserts creating 20K table + // 14.69ms // 14.43ms // 14.97ms : 200,000 inserts creating 200K table + // 199.99ms // 195.36ms // 202.05ms : 2,000,000 inserts creating 2M table + // 2234.84ms // 2187.74ms // 2240.38ms : 20,000,000 inserts creating 20M table + // 55.68ms // 53.72ms // 57.31ms : 500,000 inserts & deletes in 2K table + // 63.43ms // 61.99ms // 65.73ms : 500,000 inserts & deletes in 20K table + // 80.04ms // 77.96ms // 81.83ms : 500,000 inserts & deletes in 200K table + // 100.42ms // 97.40ms // 102.39ms : 500,000 inserts & deletes in 2M table + // 119.71ms // 120.59ms // 121.63ms : 500,000 inserts & deletes in 20M table + // 185.28ms // 195.15ms // 187.74ms : 500,000 inserts & deletes in 200M table + // 15.58ms // 14.79ms // 15.52ms : 200,000 inserts creating 200K table with varying key spacing + + return (((size_t) hash << 16 << 16) | hash) ^ seed; + } else if (len == 8 && sizeof(size_t) == 8) { + size_t hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + hash |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // avoid warning if size_t == 4 + hash ^= seed; + hash = (~hash) + (hash << 21); + hash ^= STBDS_ROTATE_RIGHT(hash,24); + hash *= 265; + hash ^= STBDS_ROTATE_RIGHT(hash,14); + hash ^= seed; + hash *= 21; + hash ^= STBDS_ROTATE_RIGHT(hash,28); + hash += (hash << 31); + hash = (~hash) + (hash << 18); + return hash; + } else { + return stbds_siphash_bytes(p,len,seed); + } +#endif +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +static int stbds_is_key_equal(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode, size_t i) +{ + if (mode >= STBDS_HM_STRING) + return 0==strcmp((char *) key, * (char **) ((char *) a + elemsize*i + keyoffset)); + else + return 0==memcmp(key, (char *) a + elemsize*i + keyoffset, keysize); +} + +#define STBDS_HASH_TO_ARR(x,elemsize) ((char*) (x) - (elemsize)) +#define STBDS_ARR_TO_HASH(x,elemsize) ((char*) (x) + (elemsize)) + +#define stbds_hash_table(a) ((stbds_hash_index *) stbds_header(a)->hash_table) + +void stbds_hmfree_func(void *a, size_t elemsize) +{ + if (a == NULL) return; + if (stbds_hash_table(a) != NULL) { + if (stbds_hash_table(a)->string.mode == STBDS_SH_STRDUP) { + size_t i; + // skip 0th element, which is default + for (i=1; i < stbds_header(a)->length; ++i) + STBDS_FREE(NULL, *(char**) ((char *) a + elemsize*i)); + } + stbds_strreset(&stbds_hash_table(a)->string); + } + STBDS_FREE(NULL, stbds_header(a)->hash_table); + STBDS_FREE(NULL, stbds_header(a)); +} + +static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + stbds_hash_index *table = stbds_hash_table(raw_a); + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t limit,i; + size_t pos; + stbds_hash_bucket *bucket; + + if (hash < 2) hash += 2; // stored hash values are forbidden from being 0, so we can detect empty slots + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket, this should help performance on small hash tables that fit in cache + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + /* NOTREACHED */ +} + +void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) +{ + size_t keyoffset = 0; + if (a == NULL) { + // make it non-empty so we can return a temp + a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + *temp = STBDS_INDEX_EMPTY; + // adjust a to point after the default element + return STBDS_ARR_TO_HASH(a,elemsize); + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + // adjust a to point to the default element + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + if (table == 0) { + *temp = -1; + } else { + ptrdiff_t slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) { + *temp = STBDS_INDEX_EMPTY; + } else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + *temp = b->index[slot & STBDS_BUCKET_MASK]; + } + } + return a; + } +} + +void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + ptrdiff_t temp; + void *p = stbds_hmget_key_ts(a, elemsize, key, keysize, &temp, mode); + stbds_temp(STBDS_HASH_TO_ARR(p,elemsize)) = temp; + return p; +} + +void * stbds_hmput_default(void *a, size_t elemsize) +{ + // three cases: + // a is NULL <- allocate + // a has a hash table but no entries, because of shmode <- grow + // a has entries <- do nothing + if (a == NULL || stbds_header(STBDS_HASH_TO_ARR(a,elemsize))->length == 0) { + a = stbds_arrgrowf(a ? STBDS_HASH_TO_ARR(a,elemsize) : NULL, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + a=STBDS_ARR_TO_HASH(a,elemsize); + } + return a; +} + +static char *stbds_strdup(char *str); + +void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + size_t keyoffset=0; + void *raw_a; + stbds_hash_index *table; + + if (a == NULL) { + a = stbds_arrgrowf(0, elemsize, 0, 1); + memset(a, 0, elemsize); + stbds_header(a)->length += 1; + // adjust a to point AFTER the default element + a = STBDS_ARR_TO_HASH(a,elemsize); + } + + // adjust a to point to the default element + raw_a = a; + a = STBDS_HASH_TO_ARR(a,elemsize); + + table = (stbds_hash_index *) stbds_header(a)->hash_table; + + if (table == NULL || table->used_count >= table->used_count_threshold) { + stbds_hash_index *nt; + size_t slot_count; + + slot_count = (table == NULL) ? STBDS_BUCKET_LENGTH : table->slot_count*2; + nt = stbds_make_hash_index(slot_count, table); + if (table) + STBDS_FREE(NULL, table); + else + nt->string.mode = mode >= STBDS_HM_STRING ? STBDS_SH_DEFAULT : 0; + stbds_header(a)->hash_table = table = nt; + STBDS_STATS(++stbds_hash_grow); + } + + // we iterate hash table explicitly because we want to track if we saw a tombstone + { + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t pos; + ptrdiff_t tombstone = -1; + stbds_hash_bucket *bucket; + + // stored hash values are forbidden from being 0, so we can detect empty slots to early out quickly + if (hash < 2) hash += 2; + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + size_t limit, i; + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + if (mode >= STBDS_HM_STRING) + stbds_temp_key(a) = * (char **) ((char *) raw_a + elemsize*bucket->index[i] + keyoffset); + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + found_empty_slot: + if (tombstone >= 0) { + pos = tombstone; + --table->tombstone_count; + } + ++table->used_count; + + { + ptrdiff_t i = (ptrdiff_t) stbds_arrlen(a); + // we want to do stbds_arraddn(1), but we can't use the macros since we don't have something of the right type + if ((size_t) i+1 > stbds_arrcap(a)) + *(void **) &a = stbds_arrgrowf(a, elemsize, 1, 0); + raw_a = STBDS_ARR_TO_HASH(a,elemsize); + + STBDS_ASSERT((size_t) i+1 <= stbds_arrcap(a)); + stbds_header(a)->length = i+1; + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + bucket->hash[pos & STBDS_BUCKET_MASK] = hash; + bucket->index[pos & STBDS_BUCKET_MASK] = i-1; + stbds_temp(a) = i-1; + + switch (table->string.mode) { + case STBDS_SH_STRDUP: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_strdup((char*) key); break; + case STBDS_SH_ARENA: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_stralloc(&table->string, (char*)key); break; + case STBDS_SH_DEFAULT: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = (char *) key; break; + default: memcpy((char *) a + elemsize*i, key, keysize); break; + } + } + return STBDS_ARR_TO_HASH(a,elemsize); + } +} + +void * stbds_shmode_func(size_t elemsize, int mode) +{ + void *a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_hash_index *h; + memset(a, 0, elemsize); + stbds_header(a)->length = 1; + stbds_header(a)->hash_table = h = (stbds_hash_index *) stbds_make_hash_index(STBDS_BUCKET_LENGTH, NULL); + h->string.mode = (unsigned char) mode; + return STBDS_ARR_TO_HASH(a,elemsize); +} + +void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + if (a == NULL) { + return 0; + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + stbds_temp(raw_a) = 0; + if (table == 0) { + return a; + } else { + ptrdiff_t slot; + slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) + return a; + else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + int i = slot & STBDS_BUCKET_MASK; + ptrdiff_t old_index = b->index[i]; + ptrdiff_t final_index = (ptrdiff_t) stbds_arrlen(raw_a)-1-1; // minus one for the raw_a vs a, and minus one for 'last' + STBDS_ASSERT(slot < (ptrdiff_t) table->slot_count); + --table->used_count; + ++table->tombstone_count; + stbds_temp(raw_a) = 1; + STBDS_ASSERT(table->used_count >= 0); + //STBDS_ASSERT(table->tombstone_count < table->slot_count/4); + b->hash[i] = STBDS_HASH_DELETED; + b->index[i] = STBDS_INDEX_DELETED; + + if (mode == STBDS_HM_STRING && table->string.mode == STBDS_SH_STRDUP) + STBDS_FREE(NULL, *(char**) ((char *) a+elemsize*old_index)); + + // if indices are the same, memcpy is a no-op, but back-pointer-fixup will fail, so skip + if (old_index != final_index) { + // swap delete + memmove((char*) a + elemsize*old_index, (char*) a + elemsize*final_index, elemsize); + + // now find the slot for the last element + if (mode == STBDS_HM_STRING) + slot = stbds_hm_find_slot(a, elemsize, *(char**) ((char *) a+elemsize*old_index + keyoffset), keysize, keyoffset, mode); + else + slot = stbds_hm_find_slot(a, elemsize, (char* ) a+elemsize*old_index + keyoffset, keysize, keyoffset, mode); + STBDS_ASSERT(slot >= 0); + b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + i = slot & STBDS_BUCKET_MASK; + STBDS_ASSERT(b->index[i] == final_index); + b->index[i] = old_index; + } + stbds_header(raw_a)->length -= 1; + + if (table->used_count < table->used_count_shrink_threshold && table->slot_count > STBDS_BUCKET_LENGTH) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count>>1, table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_shrink); + } else if (table->tombstone_count > table->tombstone_count_threshold) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count , table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_rebuild); + } + + return a; + } + } + } + /* NOTREACHED */ +} + +static char *stbds_strdup(char *str) +{ + // to keep replaceable allocator simple, we don't want to use strdup. + // rolling our own also avoids problem of strdup vs _strdup + size_t len = strlen(str)+1; + char *p = (char*) STBDS_REALLOC(NULL, 0, len); + memmove(p, str, len); + return p; +} + +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MIN +#define STBDS_STRING_ARENA_BLOCKSIZE_MIN 512u +#endif +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MAX +#define STBDS_STRING_ARENA_BLOCKSIZE_MAX (1u<<20) +#endif + +char *stbds_stralloc(stbds_string_arena *a, char *str) +{ + char *p; + size_t len = strlen(str)+1; + if (len > a->remaining) { + // compute the next blocksize + size_t blocksize = a->block; + + // size is 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, etc., so that + // there are log(SIZE) allocations to free when we destroy the table + blocksize = (size_t) (STBDS_STRING_ARENA_BLOCKSIZE_MIN) << (blocksize>>1); + + // if size is under 1M, advance to next blocktype + if (blocksize < (size_t)(STBDS_STRING_ARENA_BLOCKSIZE_MAX)) + ++a->block; + + if (len > blocksize) { + // if string is larger than blocksize, then just allocate the full size. + // note that we still advance string_block so block size will continue + // increasing, so e.g. if somebody only calls this with 1000-long strings, + // eventually the arena will start doubling and handling those as well + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + len); + memmove(sb->storage, str, len); + if (a->storage) { + // insert it after the first element, so that we don't waste the space there + sb->next = a->storage->next; + a->storage->next = sb; + } else { + sb->next = 0; + a->storage = sb; + a->remaining = 0; // this is redundant, but good for clarity + } + return sb->storage; + } else { + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + blocksize); + sb->next = a->storage; + a->storage = sb; + a->remaining = blocksize; + } + } + + STBDS_ASSERT(len <= a->remaining); + p = a->storage->storage + a->remaining - len; + a->remaining -= len; + memmove(p, str, len); + return p; +} + +void stbds_strreset(stbds_string_arena *a) +{ + stbds_string_block *x,*y; + x = a->storage; + while (x) { + y = x->next; + STBDS_FREE(NULL, x); + x = y; + } + memset(a, 0, sizeof(*a)); +} + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// UNIT TESTS +// + +#ifdef STBDS_UNIT_TESTS +#include +#ifdef STBDS_ASSERT_WAS_UNDEFINED +#undef STBDS_ASSERT +#endif +#ifndef STBDS_ASSERT +#define STBDS_ASSERT assert +#include +#endif + +typedef struct { int key,b,c,d; } stbds_struct; +typedef struct { int key[2],b,c,d; } stbds_struct2; + +static char buffer[256]; +char *strkey(int n) +{ +#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) + sprintf_s(buffer, sizeof(buffer), "test_%d", n); +#else + sprintf(buffer, "test_%d", n); +#endif + return buffer; +} + +void stbds_unit_tests(void) +{ +#if defined(_MSC_VER) && _MSC_VER <= 1200 && defined(__cplusplus) + // VC6 C++ doesn't like the template<> trick on unnamed structures, so do nothing! + STBDS_ASSERT(0); +#else + const int testsize = 100000; + const int testsize2 = testsize/20; + int *arr=NULL; + struct { int key; int value; } *intmap = NULL; + struct { char *key; int value; } *strmap = NULL, s; + struct { stbds_struct key; int value; } *map = NULL; + stbds_struct *map2 = NULL; + stbds_struct2 *map3 = NULL; + stbds_string_arena sa = { 0 }; + int key3[2] = { 1,2 }; + ptrdiff_t temp; + + int i,j; + + STBDS_ASSERT(arrlen(arr)==0); + for (i=0; i < 20000; i += 50) { + for (j=0; j < i; ++j) + arrpush(arr,j); + arrfree(arr); + } + + for (i=0; i < 4; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdel(arr,i); + arrfree(arr); + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdelswap(arr,i); + arrfree(arr); + } + + for (i=0; i < 5; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + stbds_arrins(arr,i,5); + STBDS_ASSERT(arr[i] == 5); + if (i < 4) + STBDS_ASSERT(arr[4] == 4); + arrfree(arr); + } + + i = 1; + STBDS_ASSERT(hmgeti(intmap,i) == -1); + hmdefault(intmap, -2); + STBDS_ASSERT(hmgeti(intmap, i) == -1); + STBDS_ASSERT(hmget (intmap, i) == -2); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*5); + for (i=0; i < testsize; i+=1) { + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(intmap, i, temp) == -2 ); + else STBDS_ASSERT(hmget_ts(intmap, i, temp) == i*5); + } + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=2; i < testsize; i+=4) + hmdel(intmap, i); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=0; i < testsize; i+=1) + hmdel(intmap, i); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(hmget(intmap, i) == -2 ); + hmfree(intmap); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + hmfree(intmap); + + #if defined(__clang__) || defined(__GNUC__) + #ifndef __cplusplus + intmap = NULL; + hmput(intmap, 15, 7); + hmput(intmap, 11, 3); + hmput(intmap, 9, 5); + STBDS_ASSERT(hmget(intmap, 9) == 5); + STBDS_ASSERT(hmget(intmap, 11) == 3); + STBDS_ASSERT(hmget(intmap, 15) == 7); + #endif + #endif + + for (i=0; i < testsize; ++i) + stralloc(&sa, strkey(i)); + strreset(&sa); + + { + s.key = "a", s.value = 1; + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key == s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_strdup(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_arena(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + for (j=0; j < 2; ++j) { + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + if (j == 0) + sh_new_strdup(strmap); + else + sh_new_arena(strmap); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + shdefault(strmap, -2); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + for (i=0; i < testsize; i+=2) + shput(strmap, strkey(i), i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=2; i < testsize; i+=4) + shdel(strmap, strkey(i)); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=0; i < testsize; i+=1) + shdel(strmap, strkey(i)); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + shfree(strmap); + } + + { + struct { char *key; char value; } *hash = NULL; + char name[4] = "jen"; + shput(hash, "bob" , 'h'); + shput(hash, "sally" , 'e'); + shput(hash, "fred" , 'l'); + shput(hash, "jen" , 'x'); + shput(hash, "doug" , 'o'); + + shput(hash, name , 'l'); + shfree(hash); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmput(map, s, i*5); + } + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3 ,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmget(map, s) == 0); + else STBDS_ASSERT(hmget(map, s) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(map, s, temp) == 0); + else STBDS_ASSERT(hmget_ts(map, s, temp) == i*5); + //STBDS_ASSERT(hmget(map, t.key) == 0); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmputs(map2, s); + } + hmfree(map); + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmgets(map2, s.key).d == 0); + else STBDS_ASSERT(hmgets(map2, s.key).d == i*4); + //STBDS_ASSERT(hmgetp(map2, t.key) == 0); + } + hmfree(map2); + + for (i=0; i < testsize; i += 2) { + stbds_struct2 s = { { i,i*2 }, i*3,i*4, i*5 }; + hmputs(map3, s); + } + for (i=0; i < testsize; i += 1) { + stbds_struct2 s = { { i,i*2}, i*3, i*4, i*5 }; + stbds_struct2 t = { { i,i*2}, i*3+1, i*4, i*5 }; + if (i & 1) STBDS_ASSERT(hmgets(map3, s.key).d == 0); + else STBDS_ASSERT(hmgets(map3, s.key).d == i*5); + //STBDS_ASSERT(hmgetp(map3, t.key) == 0); + } +#endif +} +#endif + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2019 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/tools/common/subprocess.h b/tools/common/subprocess.h new file mode 100644 index 0000000000..8fd51b643f --- /dev/null +++ b/tools/common/subprocess.h @@ -0,0 +1,1168 @@ +/* + The latest version of this library is available on GitHub; + https://github.com/sheredom/subprocess.h +*/ + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ + +#ifndef SHEREDOM_SUBPROCESS_H_INCLUDED +#define SHEREDOM_SUBPROCESS_H_INCLUDED + +#if defined(_MSC_VER) +#pragma warning(push, 1) + +/* disable warning: '__cplusplus' is not defined as a preprocessor macro, + * replacing with '0' for '#if/#elif' */ +#pragma warning(disable : 4668) +#endif + +#include +#include + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(_MSC_VER) +#define subprocess_pure +#define subprocess_weak __inline +#define subprocess_tls __declspec(thread) +#elif defined(__clang__) || defined(__GNUC__) +#define subprocess_pure __attribute__((pure)) +#define subprocess_weak __attribute__((weak)) +#define subprocess_tls __thread +#else +#error Non clang, non gcc, non MSVC compiler found! +#endif + +struct subprocess_s; + +enum subprocess_option_e { + // stdout and stderr are the same FILE. + subprocess_option_combined_stdout_stderr = 0x1, + + // The child process should inherit the environment variables of the parent. + subprocess_option_inherit_environment = 0x2, + + // Enable asynchronous reading of stdout/stderr before it has completed. + subprocess_option_enable_async = 0x4, + + // Enable the child process to be spawned with no window visible if supported + // by the platform. + subprocess_option_no_window = 0x8, + + // Search for program names in the PATH variable. Always enabled on Windows. + // Note: this will **not** search for paths in any provided custom environment + // and instead uses the PATH of the spawning process. + subprocess_option_search_user_path = 0x10 +}; + +#if defined(__cplusplus) +extern "C" { +#endif + +/// @brief Create a process. +/// @param command_line An array of strings for the command line to execute for +/// this process. The last element must be NULL to signify the end of the array. +/// The memory backing this parameter only needs to persist until this function +/// returns. +/// @param options A bit field of subprocess_option_e's to pass. +/// @param out_process The newly created process. +/// @return On success zero is returned. +subprocess_weak int subprocess_create(const char *const command_line[], + int options, + struct subprocess_s *const out_process); + +/// @brief Create a process (extended create). +/// @param command_line An array of strings for the command line to execute for +/// this process. The last element must be NULL to signify the end of the array. +/// The memory backing this parameter only needs to persist until this function +/// returns. +/// @param options A bit field of subprocess_option_e's to pass. +/// @param environment An optional array of strings for the environment to use +/// for a child process (each element of the form FOO=BAR). The last element +/// must be NULL to signify the end of the array. +/// @param out_process The newly created process. +/// @return On success zero is returned. +/// +/// If `options` contains `subprocess_option_inherit_environment`, then +/// `environment` must be NULL. +subprocess_weak int +subprocess_create_ex(const char *const command_line[], int options, + const char *const environment[], + struct subprocess_s *const out_process); + +/// @brief Get the standard input file for a process. +/// @param process The process to query. +/// @return The file for standard input of the process. +/// +/// The file returned can be written to by the parent process to feed data to +/// the standard input of the process. +subprocess_pure subprocess_weak FILE * +subprocess_stdin(const struct subprocess_s *const process); + +/// @brief Get the standard output file for a process. +/// @param process The process to query. +/// @return The file for standard output of the process. +/// +/// The file returned can be read from by the parent process to read data from +/// the standard output of the child process. +subprocess_pure subprocess_weak FILE * +subprocess_stdout(const struct subprocess_s *const process); + +/// @brief Get the standard error file for a process. +/// @param process The process to query. +/// @return The file for standard error of the process. +/// +/// The file returned can be read from by the parent process to read data from +/// the standard error of the child process. +/// +/// If the process was created with the subprocess_option_combined_stdout_stderr +/// option bit set, this function will return NULL, and the subprocess_stdout +/// function should be used for both the standard output and error combined. +subprocess_pure subprocess_weak FILE * +subprocess_stderr(const struct subprocess_s *const process); + +/// @brief Wait for a process to finish execution. +/// @param process The process to wait for. +/// @param out_return_code The return code of the returned process (can be +/// NULL). +/// @return On success zero is returned. +/// +/// Joining a process will close the stdin pipe to the process. +subprocess_weak int subprocess_join(struct subprocess_s *const process, + int *const out_return_code); + +/// @brief Destroy a previously created process. +/// @param process The process to destroy. +/// @return On success zero is returned. +/// +/// If the process to be destroyed had not finished execution, it may out live +/// the parent process. +subprocess_weak int subprocess_destroy(struct subprocess_s *const process); + +/// @brief Terminate a previously created process. +/// @param process The process to terminate. +/// @return On success zero is returned. +/// +/// If the process to be destroyed had not finished execution, it will be +/// terminated (i.e killed). +subprocess_weak int subprocess_terminate(struct subprocess_s *const process); + +/// @brief Read the standard output from the child process. +/// @param process The process to read from. +/// @param buffer The buffer to read into. +/// @param size The maximum number of bytes to read. +/// @return The number of bytes actually read into buffer. Can only be 0 if the +/// process has complete. +/// +/// The only safe way to read from the standard output of a process during it's +/// execution is to use the `subprocess_option_enable_async` option in +/// conjuction with this method. +subprocess_weak unsigned +subprocess_read_stdout(struct subprocess_s *const process, char *const buffer, + unsigned size); + +/// @brief Read the standard error from the child process. +/// @param process The process to read from. +/// @param buffer The buffer to read into. +/// @param size The maximum number of bytes to read. +/// @return The number of bytes actually read into buffer. Can only be 0 if the +/// process has complete. +/// +/// The only safe way to read from the standard error of a process during it's +/// execution is to use the `subprocess_option_enable_async` option in +/// conjuction with this method. +subprocess_weak unsigned +subprocess_read_stderr(struct subprocess_s *const process, char *const buffer, + unsigned size); + +/// @brief Returns if the subprocess is currently still alive and executing. +/// @param process The process to check. +/// @return If the process is still alive non-zero is returned. +subprocess_weak int subprocess_alive(struct subprocess_s *const process); + +#if defined(__cplusplus) +#define SUBPROCESS_CAST(type, x) static_cast(x) +#define SUBPROCESS_PTR_CAST(type, x) reinterpret_cast(x) +#define SUBPROCESS_CONST_CAST(type, x) const_cast(x) +#define SUBPROCESS_NULL NULL +#else +#define SUBPROCESS_CAST(type, x) ((type)(x)) +#define SUBPROCESS_PTR_CAST(type, x) ((type)(x)) +#define SUBPROCESS_CONST_CAST(type, x) ((type)(x)) +#define SUBPROCESS_NULL 0 +#endif + +#if !defined(_WIN32) +#include +#include +#include +#include +#include +#include +#endif + +#if defined(_WIN32) + +#if (_MSC_VER < 1920) +#ifdef _WIN64 +typedef __int64 subprocess_intptr_t; +typedef unsigned __int64 subprocess_size_t; +#else +typedef int subprocess_intptr_t; +typedef unsigned int subprocess_size_t; +#endif +#else +#include + +typedef intptr_t subprocess_intptr_t; +typedef size_t subprocess_size_t; +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-identifier" +#endif + +typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION; +typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES; +typedef struct _STARTUPINFOA *LPSTARTUPINFOA; +typedef struct _OVERLAPPED *LPOVERLAPPED; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#if defined(_MSC_VER) +#pragma warning(push, 1) +#endif +struct subprocess_subprocess_information_s { + void *hProcess; + void *hThread; + unsigned long dwProcessId; + unsigned long dwThreadId; +}; + +struct subprocess_security_attributes_s { + unsigned long nLength; + void *lpSecurityDescriptor; + int bInheritHandle; +}; + +struct subprocess_startup_info_s { + unsigned long cb; + char *lpReserved; + char *lpDesktop; + char *lpTitle; + unsigned long dwX; + unsigned long dwY; + unsigned long dwXSize; + unsigned long dwYSize; + unsigned long dwXCountChars; + unsigned long dwYCountChars; + unsigned long dwFillAttribute; + unsigned long dwFlags; + unsigned short wShowWindow; + unsigned short cbReserved2; + unsigned char *lpReserved2; + void *hStdInput; + void *hStdOutput; + void *hStdError; +}; + +struct subprocess_overlapped_s { + uintptr_t Internal; + uintptr_t InternalHigh; + union { + struct { + unsigned long Offset; + unsigned long OffsetHigh; + } DUMMYSTRUCTNAME; + void *Pointer; + } DUMMYUNIONNAME; + + void *hEvent; +}; + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +__declspec(dllimport) unsigned long __stdcall GetLastError(void); +__declspec(dllimport) int __stdcall SetHandleInformation(void *, unsigned long, + unsigned long); +__declspec(dllimport) int __stdcall CreatePipe(void **, void **, + LPSECURITY_ATTRIBUTES, + unsigned long); +__declspec(dllimport) void *__stdcall CreateNamedPipeA( + const char *, unsigned long, unsigned long, unsigned long, unsigned long, + unsigned long, unsigned long, LPSECURITY_ATTRIBUTES); +__declspec(dllimport) int __stdcall ReadFile(void *, void *, unsigned long, + unsigned long *, LPOVERLAPPED); +__declspec(dllimport) unsigned long __stdcall GetCurrentProcessId(void); +__declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void); +__declspec(dllimport) void *__stdcall CreateFileA(const char *, unsigned long, + unsigned long, + LPSECURITY_ATTRIBUTES, + unsigned long, unsigned long, + void *); +__declspec(dllimport) void *__stdcall CreateEventA(LPSECURITY_ATTRIBUTES, int, + int, const char *); +__declspec(dllimport) int __stdcall CreateProcessA( + const char *, char *, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, int, + unsigned long, void *, const char *, LPSTARTUPINFOA, LPPROCESS_INFORMATION); +__declspec(dllimport) int __stdcall CloseHandle(void *); +__declspec(dllimport) unsigned long __stdcall WaitForSingleObject( + void *, unsigned long); +__declspec(dllimport) int __stdcall GetExitCodeProcess( + void *, unsigned long *lpExitCode); +__declspec(dllimport) int __stdcall TerminateProcess(void *, unsigned int); +__declspec(dllimport) unsigned long __stdcall WaitForMultipleObjects( + unsigned long, void *const *, int, unsigned long); +__declspec(dllimport) int __stdcall GetOverlappedResult(void *, LPOVERLAPPED, + unsigned long *, int); + +#if defined(_DLL) +#define SUBPROCESS_DLLIMPORT __declspec(dllimport) +#else +#define SUBPROCESS_DLLIMPORT +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-identifier" +#endif + +SUBPROCESS_DLLIMPORT int __cdecl _fileno(FILE *); +SUBPROCESS_DLLIMPORT int __cdecl _open_osfhandle(subprocess_intptr_t, int); +SUBPROCESS_DLLIMPORT subprocess_intptr_t __cdecl _get_osfhandle(int); + +#ifndef __MINGW32__ +void *__cdecl _alloca(subprocess_size_t); +#else +#include // alloca +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#else +typedef size_t subprocess_size_t; +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif +struct subprocess_s { + FILE *stdin_file; + FILE *stdout_file; + FILE *stderr_file; + +#if defined(_WIN32) + void *hProcess; + void *hStdInput; + void *hEventOutput; + void *hEventError; +#else + pid_t child; + int return_status; +#endif + + subprocess_size_t alive; +}; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#if defined(_WIN32) +subprocess_weak int subprocess_create_named_pipe_helper(void **rd, void **wr); +int subprocess_create_named_pipe_helper(void **rd, void **wr) { + const unsigned long pipeAccessInbound = 0x00000001; + const unsigned long fileFlagOverlapped = 0x40000000; + const unsigned long pipeTypeByte = 0x00000000; + const unsigned long pipeWait = 0x00000000; + const unsigned long genericWrite = 0x40000000; + const unsigned long openExisting = 3; + const unsigned long fileAttributeNormal = 0x00000080; + const void *const invalidHandleValue = + SUBPROCESS_PTR_CAST(void *, ~(SUBPROCESS_CAST(subprocess_intptr_t, 0))); + struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), + SUBPROCESS_NULL, 1}; + char name[256] = {0}; + static subprocess_tls long index = 0; + const long unique = index++; + +#if defined(_MSC_VER) && _MSC_VER < 1900 +#pragma warning(push, 1) +#pragma warning(disable : 4996) + _snprintf(name, sizeof(name) - 1, + "\\\\.\\pipe\\sheredom_subprocess_h.%08lx.%08lx.%ld", + GetCurrentProcessId(), GetCurrentThreadId(), unique); +#pragma warning(pop) +#else + snprintf(name, sizeof(name) - 1, + "\\\\.\\pipe\\sheredom_subprocess_h.%08lx.%08lx.%ld", + GetCurrentProcessId(), GetCurrentThreadId(), unique); +#endif + + *rd = + CreateNamedPipeA(name, pipeAccessInbound | fileFlagOverlapped, + pipeTypeByte | pipeWait, 1, 4096, 4096, 0, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr)); + + if (invalidHandleValue == *rd) { + return -1; + } + + *wr = CreateFileA(name, genericWrite, 0, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), + openExisting, fileAttributeNormal, SUBPROCESS_NULL); + + if (invalidHandleValue == *wr) { + return -1; + } + + return 0; +} +#endif + +int subprocess_create(const char *const commandLine[], int options, + struct subprocess_s *const out_process) { + return subprocess_create_ex(commandLine, options, SUBPROCESS_NULL, + out_process); +} + +int subprocess_create_ex(const char *const commandLine[], int options, + const char *const environment[], + struct subprocess_s *const out_process) { +#if defined(_WIN32) + int fd; + void *rd, *wr; + char *commandLineCombined; + subprocess_size_t len; + int i, j; + int need_quoting; + unsigned long flags = 0; + const unsigned long startFUseStdHandles = 0x00000100; + const unsigned long handleFlagInherit = 0x00000001; + const unsigned long createNoWindow = 0x08000000; + struct subprocess_subprocess_information_s processInfo; + struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), + SUBPROCESS_NULL, 1}; + char *used_environment = SUBPROCESS_NULL; + struct subprocess_startup_info_s startInfo = {0, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL}; + + startInfo.cb = sizeof(startInfo); + startInfo.dwFlags = startFUseStdHandles; + + if (subprocess_option_no_window == (options & subprocess_option_no_window)) { + flags |= createNoWindow; + } + + if (subprocess_option_inherit_environment != + (options & subprocess_option_inherit_environment)) { + if (SUBPROCESS_NULL == environment) { + used_environment = SUBPROCESS_CONST_CAST(char *, "\0\0"); + } else { + // We always end with two null terminators. + len = 2; + + for (i = 0; environment[i]; i++) { + for (j = 0; '\0' != environment[i][j]; j++) { + len++; + } + + // For the null terminator too. + len++; + } + + used_environment = SUBPROCESS_CAST(char *, _alloca(len)); + + // Re-use len for the insertion position + len = 0; + + for (i = 0; environment[i]; i++) { + for (j = 0; '\0' != environment[i][j]; j++) { + used_environment[len++] = environment[i][j]; + } + + used_environment[len++] = '\0'; + } + + // End with the two null terminators. + used_environment[len++] = '\0'; + used_environment[len++] = '\0'; + } + } else { + if (SUBPROCESS_NULL != environment) { + return -1; + } + } + + if (!CreatePipe(&rd, &wr, SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), + 0)) { + return -1; + } + + if (!SetHandleInformation(wr, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, wr), 0); + + if (-1 != fd) { + out_process->stdin_file = _fdopen(fd, "wb"); + + if (SUBPROCESS_NULL == out_process->stdin_file) { + return -1; + } + } + + startInfo.hStdInput = rd; + + if (options & subprocess_option_enable_async) { + if (subprocess_create_named_pipe_helper(&rd, &wr)) { + return -1; + } + } else { + if (!CreatePipe(&rd, &wr, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) { + return -1; + } + } + + if (!SetHandleInformation(rd, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0); + + if (-1 != fd) { + out_process->stdout_file = _fdopen(fd, "rb"); + + if (SUBPROCESS_NULL == out_process->stdout_file) { + return -1; + } + } + + startInfo.hStdOutput = wr; + + if (subprocess_option_combined_stdout_stderr == + (options & subprocess_option_combined_stdout_stderr)) { + out_process->stderr_file = out_process->stdout_file; + startInfo.hStdError = startInfo.hStdOutput; + } else { + if (options & subprocess_option_enable_async) { + if (subprocess_create_named_pipe_helper(&rd, &wr)) { + return -1; + } + } else { + if (!CreatePipe(&rd, &wr, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) { + return -1; + } + } + + if (!SetHandleInformation(rd, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0); + + if (-1 != fd) { + out_process->stderr_file = _fdopen(fd, "rb"); + + if (SUBPROCESS_NULL == out_process->stderr_file) { + return -1; + } + } + + startInfo.hStdError = wr; + } + + if (options & subprocess_option_enable_async) { + out_process->hEventOutput = + CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, + SUBPROCESS_NULL); + out_process->hEventError = + CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, + SUBPROCESS_NULL); + } else { + out_process->hEventOutput = SUBPROCESS_NULL; + out_process->hEventError = SUBPROCESS_NULL; + } + + // Combine commandLine together into a single string + len = 0; + for (i = 0; commandLine[i]; i++) { + // for the trailing \0 + len++; + + // Quote the argument if it has a space in it + if (strpbrk(commandLine[i], "\t\v ") != SUBPROCESS_NULL) + len += 2; + + for (j = 0; '\0' != commandLine[i][j]; j++) { + switch (commandLine[i][j]) { + default: + break; + case '\\': + if (commandLine[i][j + 1] == '"') { + len++; + } + + break; + case '"': + len++; + break; + } + len++; + } + } + + commandLineCombined = SUBPROCESS_CAST(char *, _alloca(len)); + + if (!commandLineCombined) { + return -1; + } + + // Gonna re-use len to store the write index into commandLineCombined + len = 0; + + for (i = 0; commandLine[i]; i++) { + if (0 != i) { + commandLineCombined[len++] = ' '; + } + + need_quoting = strpbrk(commandLine[i], "\t\v ") != SUBPROCESS_NULL; + if (need_quoting) { + commandLineCombined[len++] = '"'; + } + + for (j = 0; '\0' != commandLine[i][j]; j++) { + switch (commandLine[i][j]) { + default: + break; + case '\\': + if (commandLine[i][j + 1] == '"') { + commandLineCombined[len++] = '\\'; + } + + break; + case '"': + commandLineCombined[len++] = '\\'; + break; + } + + commandLineCombined[len++] = commandLine[i][j]; + } + if (need_quoting) { + commandLineCombined[len++] = '"'; + } + } + + commandLineCombined[len] = '\0'; + + if (!CreateProcessA( + SUBPROCESS_NULL, + commandLineCombined, // command line + SUBPROCESS_NULL, // process security attributes + SUBPROCESS_NULL, // primary thread security attributes + 1, // handles are inherited + flags, // creation flags + used_environment, // used environment + SUBPROCESS_NULL, // use parent's current directory + SUBPROCESS_PTR_CAST(LPSTARTUPINFOA, + &startInfo), // STARTUPINFO pointer + SUBPROCESS_PTR_CAST(LPPROCESS_INFORMATION, &processInfo))) { + return -1; + } + + out_process->hProcess = processInfo.hProcess; + + out_process->hStdInput = startInfo.hStdInput; + + // We don't need the handle of the primary thread in the called process. + CloseHandle(processInfo.hThread); + + if (SUBPROCESS_NULL != startInfo.hStdOutput) { + CloseHandle(startInfo.hStdOutput); + + if (startInfo.hStdError != startInfo.hStdOutput) { + CloseHandle(startInfo.hStdError); + } + } + + out_process->alive = 1; + + return 0; +#else + int stdinfd[2]; + int stdoutfd[2]; + int stderrfd[2]; + pid_t child; + extern char **environ; + char *const empty_environment[1] = {SUBPROCESS_NULL}; + posix_spawn_file_actions_t actions; + char *const *used_environment; + + if (subprocess_option_inherit_environment == + (options & subprocess_option_inherit_environment)) { + if (SUBPROCESS_NULL != environment) { + return -1; + } + } + + if (0 != pipe(stdinfd)) { + return -1; + } + + if (0 != pipe(stdoutfd)) { + return -1; + } + + if (subprocess_option_combined_stdout_stderr != + (options & subprocess_option_combined_stdout_stderr)) { + if (0 != pipe(stderrfd)) { + return -1; + } + } + + if (environment) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-qual" +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + used_environment = (char *const *)environment; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } else if (subprocess_option_inherit_environment == + (options & subprocess_option_inherit_environment)) { + used_environment = environ; + } else { + used_environment = empty_environment; + } + + if (0 != posix_spawn_file_actions_init(&actions)) { + return -1; + } + + // Close the stdin write end + if (0 != posix_spawn_file_actions_addclose(&actions, stdinfd[1])) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + + // Map the read end to stdin + if (0 != + posix_spawn_file_actions_adddup2(&actions, stdinfd[0], STDIN_FILENO)) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + + // Close the stdout read end + if (0 != posix_spawn_file_actions_addclose(&actions, stdoutfd[0])) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + + // Map the write end to stdout + if (0 != + posix_spawn_file_actions_adddup2(&actions, stdoutfd[1], STDOUT_FILENO)) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + + if (subprocess_option_combined_stdout_stderr == + (options & subprocess_option_combined_stdout_stderr)) { + if (0 != posix_spawn_file_actions_adddup2(&actions, STDOUT_FILENO, + STDERR_FILENO)) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + } else { + // Close the stderr read end + if (0 != posix_spawn_file_actions_addclose(&actions, stderrfd[0])) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + // Map the write end to stdout + if (0 != posix_spawn_file_actions_adddup2(&actions, stderrfd[1], + STDERR_FILENO)) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-qual" +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + if (subprocess_option_search_user_path == + (options & subprocess_option_search_user_path)) { + if (0 != posix_spawnp(&child, commandLine[0], &actions, SUBPROCESS_NULL, + (char *const *)commandLine, used_environment)) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + } else { + if (0 != posix_spawn(&child, commandLine[0], &actions, SUBPROCESS_NULL, + (char *const *)commandLine, used_environment)) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + // Close the stdin read end + close(stdinfd[0]); + // Store the stdin write end + out_process->stdin_file = fdopen(stdinfd[1], "wb"); + + // Close the stdout write end + close(stdoutfd[1]); + // Store the stdout read end + out_process->stdout_file = fdopen(stdoutfd[0], "rb"); + + if (subprocess_option_combined_stdout_stderr == + (options & subprocess_option_combined_stdout_stderr)) { + out_process->stderr_file = out_process->stdout_file; + } else { + // Close the stderr write end + close(stderrfd[1]); + // Store the stderr read end + out_process->stderr_file = fdopen(stderrfd[0], "rb"); + } + + // Store the child's pid + out_process->child = child; + + out_process->alive = 1; + + posix_spawn_file_actions_destroy(&actions); + return 0; +#endif +} + +FILE *subprocess_stdin(const struct subprocess_s *const process) { + return process->stdin_file; +} + +FILE *subprocess_stdout(const struct subprocess_s *const process) { + return process->stdout_file; +} + +FILE *subprocess_stderr(const struct subprocess_s *const process) { + if (process->stdout_file != process->stderr_file) { + return process->stderr_file; + } else { + return SUBPROCESS_NULL; + } +} + +int subprocess_join(struct subprocess_s *const process, + int *const out_return_code) { +#if defined(_WIN32) + const unsigned long infinite = 0xFFFFFFFF; + + if (process->stdin_file) { + fclose(process->stdin_file); + process->stdin_file = SUBPROCESS_NULL; + } + + if (process->hStdInput) { + CloseHandle(process->hStdInput); + process->hStdInput = SUBPROCESS_NULL; + } + + WaitForSingleObject(process->hProcess, infinite); + + if (out_return_code) { + if (!GetExitCodeProcess( + process->hProcess, + SUBPROCESS_PTR_CAST(unsigned long *, out_return_code))) { + return -1; + } + } + + process->alive = 0; + + return 0; +#else + int status; + + if (process->stdin_file) { + fclose(process->stdin_file); + process->stdin_file = SUBPROCESS_NULL; + } + + if (process->child) { + if (process->child != waitpid(process->child, &status, 0)) { + return -1; + } + + process->child = 0; + + if (WIFEXITED(status)) { + process->return_status = WEXITSTATUS(status); + } else { + process->return_status = EXIT_FAILURE; + } + + process->alive = 0; + } + + if (out_return_code) { + *out_return_code = process->return_status; + } + + return 0; +#endif +} + +int subprocess_destroy(struct subprocess_s *const process) { + if (process->stdin_file) { + fclose(process->stdin_file); + process->stdin_file = SUBPROCESS_NULL; + } + + if (process->stdout_file) { + fclose(process->stdout_file); + + if (process->stdout_file != process->stderr_file) { + fclose(process->stderr_file); + } + + process->stdout_file = SUBPROCESS_NULL; + process->stderr_file = SUBPROCESS_NULL; + } + +#if defined(_WIN32) + if (process->hProcess) { + CloseHandle(process->hProcess); + process->hProcess = SUBPROCESS_NULL; + + if (process->hStdInput) { + CloseHandle(process->hStdInput); + } + + if (process->hEventOutput) { + CloseHandle(process->hEventOutput); + } + + if (process->hEventError) { + CloseHandle(process->hEventError); + } + } +#endif + + return 0; +} + +int subprocess_terminate(struct subprocess_s *const process) { +#if defined(_WIN32) + unsigned int killed_process_exit_code; + int success_terminate; + int windows_call_result; + + killed_process_exit_code = 99; + windows_call_result = + TerminateProcess(process->hProcess, killed_process_exit_code); + success_terminate = (windows_call_result == 0) ? 1 : 0; + return success_terminate; +#else + int result; + result = kill(process->child, 9); + return result; +#endif +} + +unsigned subprocess_read_stdout(struct subprocess_s *const process, + char *const buffer, unsigned size) { +#if defined(_WIN32) + void *handle; + unsigned long bytes_read = 0; + struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL}; + overlapped.hEvent = process->hEventOutput; + + handle = SUBPROCESS_PTR_CAST(void *, + _get_osfhandle(_fileno(process->stdout_file))); + + if (!ReadFile(handle, buffer, size, &bytes_read, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) { + const unsigned long errorIoPending = 997; + unsigned long error = GetLastError(); + + // Means we've got an async read! + if (error == errorIoPending) { + if (!GetOverlappedResult(handle, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped), + &bytes_read, 1)) { + const unsigned long errorIoIncomplete = 996; + const unsigned long errorHandleEOF = 38; + error = GetLastError(); + + if ((error != errorIoIncomplete) && (error != errorHandleEOF)) { + return 0; + } + } + } + } + + return SUBPROCESS_CAST(unsigned, bytes_read); +#else + const int fd = fileno(process->stdout_file); + const ssize_t bytes_read = read(fd, buffer, size); + + if (bytes_read < 0) { + return 0; + } + + return SUBPROCESS_CAST(unsigned, bytes_read); +#endif +} + +unsigned subprocess_read_stderr(struct subprocess_s *const process, + char *const buffer, unsigned size) { +#if defined(_WIN32) + void *handle; + unsigned long bytes_read = 0; + struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL}; + overlapped.hEvent = process->hEventError; + + handle = SUBPROCESS_PTR_CAST(void *, + _get_osfhandle(_fileno(process->stderr_file))); + + if (!ReadFile(handle, buffer, size, &bytes_read, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) { + const unsigned long errorIoPending = 997; + unsigned long error = GetLastError(); + + // Means we've got an async read! + if (error == errorIoPending) { + if (!GetOverlappedResult(handle, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped), + &bytes_read, 1)) { + const unsigned long errorIoIncomplete = 996; + const unsigned long errorHandleEOF = 38; + error = GetLastError(); + + if ((error != errorIoIncomplete) && (error != errorHandleEOF)) { + return 0; + } + } + } + } + + return SUBPROCESS_CAST(unsigned, bytes_read); +#else + const int fd = fileno(process->stderr_file); + const ssize_t bytes_read = read(fd, buffer, size); + + if (bytes_read < 0) { + return 0; + } + + return SUBPROCESS_CAST(unsigned, bytes_read); +#endif +} + +int subprocess_alive(struct subprocess_s *const process) { + int is_alive = SUBPROCESS_CAST(int, process->alive); + + if (!is_alive) { + return 0; + } +#if defined(_WIN32) + { + const unsigned long zero = 0x0; + const unsigned long wait_object_0 = 0x00000000L; + + is_alive = wait_object_0 != WaitForSingleObject(process->hProcess, zero); + } +#else + { + int status; + is_alive = 0 == waitpid(process->child, &status, WNOHANG); + + // If the process was successfully waited on we need to cleanup now. + if (!is_alive) { + if (WIFEXITED(status)) { + process->return_status = WEXITSTATUS(status); + } else { + process->return_status = EXIT_FAILURE; + } + + // Since we've already successfully waited on the process, we need to wipe + // the child now. + process->child = 0; + + if (subprocess_join(process, SUBPROCESS_NULL)) { + return -1; + } + } + } +#endif + + if (!is_alive) { + process->alive = 0; + } + + return is_alive; +} + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif /* SHEREDOM_SUBPROCESS_H_INCLUDED */ diff --git a/tools/common/utils.h b/tools/common/utils.h new file mode 100644 index 0000000000..6d1cd4b4cf --- /dev/null +++ b/tools/common/utils.h @@ -0,0 +1,179 @@ +#ifndef LIBDRAGON_TOOLS_UTILS_H +#define LIBDRAGON_TOOLS_UTILS_H + +#include "polyfill.h" +#include // memcpy +#include +#include +#include +#include +#include + +/** + * Misc utilities functions and macros. Internal header. + */ + +#define SWAP(a, b) ({ typeof(a) t = a; a = b; b = t; }) + +#define MAX(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a > _b ? _a : _b; }) +#define MIN(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a < _b ? _a : _b; }) +#define CLAMP(x, min, max) (MIN(MAX((x), (min)), (max))) + +/** @brief Round n up to the next multiple of d */ +#define ROUND_UP(n, d) ({ \ + typeof(n) _n = n; typeof(d) _d = d; \ + (((_n) + (_d) - 1) / (_d) * (_d)); \ +}) + +/** @brief Round n down to the previous multiple of d */ +#define ROUND_DOWN(n, d) ({ \ + typeof(n) _n = n; typeof(d) _d = d; \ + ((_n >= 0) ? ((_n) / (_d) * (_d)) : -(((-_n + (_d) - 1) / (_d)) * (_d))); \ +}) + +/** @brief Return the ceil of n/d */ +#define DIVIDE_CEIL(n, d) ({ \ + typeof(n) _n = n; typeof(d) _d = d; \ + ((_n) + (_d) - 1) / (_d); \ +}) + +/** @brief Absolute number */ +#define ABS(x) ({ \ + typeof(x) _x = x; \ + (_x < 0 ? -_x : _x); \ +}) + +/** @brief Type-safe bitcast from float to integer */ +#define F2I(f) ({ uint32_t __i; memcpy(&__i, &(f), 4); __i; }) + +/** @brief Type-safe bitcast from integer to float */ +#define I2F(i) ({ float __f; memcpy(&__f, &(i), 4); __f; }) + +/** @brief Hint for the compiler that the condition is likely to happen */ +#define LIKELY(cond) __builtin_expect(!!(cond), 1) +/** @brief Hint for the compiler that the condition is unlikely to happen */ +#define UNLIKELY(cond) __builtin_expect(!!(cond), 0) + +/** @brief UTF-8 decoding */ +uint32_t __utf8_decode(const char **str); + +__attribute__((used)) +static char* path_remove_trailing_slash(char *path) +{ + path = strdup(path); + int n = strlen(path); + if (path[n-1] == '/' || path[n-1] == '\\') + path[n-1] = 0; + return path; +} + +__attribute__((used)) +static char *change_ext(const char *fn, const char *ext) +{ + char *out = strdup(fn); + char *dot = strrchr(out, '.'); + if (dot) *dot = 0; + strcat(out, ext); + return out; +} + +__attribute__((used)) +static bool file_exists(const char *filename) +{ + FILE *f = fopen(filename, "r"); + if (f) fclose(f); + return f != NULL; +} + +// Find the directory where the libdragon toolchain is installed. +// This is where you can find GCC, the linker, etc. +__attribute__((used)) +static const char *n64_toolchain_dir(void) +{ + static char *n64_inst = NULL; + if (n64_inst) + return n64_inst; + + // Find the toolchain installation directory. + // n64.mk supports having a separate installation for the toolchain and + // libdragon. So first check if N64_GCCPREFIX is set; if so the toolchain + // is there. Otherwise, fallback to N64_INST which is where we expect + // the toolchain to reside. + n64_inst = getenv("N64_GCCPREFIX"); + if (!n64_inst) + n64_inst = getenv("N64_INST"); + if (!n64_inst) + return NULL; + + // Remove the trailing backslash if any. On some system, running + // popen with a path containing double backslashes will fail, so + // we normalize it here. + n64_inst = path_remove_trailing_slash(n64_inst); + return n64_inst; +} + +// Find the directory where the libdragon tools are installed. +// This is where you can find mksprite, mkfont, etc. +__attribute__((used)) +static const char *n64_tools_dir(void) +{ + static char *n64_inst = NULL; + if (n64_inst) + return n64_inst; + + // Find the tools installation directory. + n64_inst = getenv("N64_INST"); + if (!n64_inst) + return NULL; + + // Remove the trailing backslash if any. On some system, running + // popen with a path containing double backslashes will fail, so + // we normalize it here. + n64_inst = path_remove_trailing_slash(n64_inst); + return n64_inst; +} + +__attribute__((used)) +static uint8_t* slurp(const char *fn, int *size) +{ + FILE *f = fopen(fn, "rb"); + if (!f) return NULL; + fseek(f, 0, SEEK_END); + int sz = ftell(f); + fseek(f, 0, SEEK_SET); + uint8_t *buf = (uint8_t*)malloc(sz); + fread(buf, 1, sz, f); + fclose(f); + if (size) *size = sz; + return buf; +} + +#ifdef __cplusplus +#include +__attribute__((used)) +static std::vector slurp(const char *fn) +{ + std::vector ret; + FILE *f = fopen(fn, "rb"); + if (!f) return ret; + fseek(f, 0, SEEK_END); + ret.resize(ftell(f)); + fseek(f, 0, SEEK_SET); + fread(&ret[0], 1, ret.size(), f); + fclose(f); + return ret; +} +#endif + +__attribute__((used)) +static void forward_to_stderr(FILE *log, const char *prefix) +{ + char *line = 0; size_t linesize = 0; + while (getline(&line, &linesize, log) != -1) { + fputs(prefix, stderr); + fputs(line, stderr); + } + free(line); +} + +#endif diff --git a/tools/mapPacker.py b/tools/mapPacker.py deleted file mode 100644 index 4ac4b66930..0000000000 --- a/tools/mapPacker.py +++ /dev/null @@ -1,50 +0,0 @@ -import sys, struct, subprocess - -class MapEntry(): - def __init__(self, nm, addr): - self.name = nm - self.addr = addr - self.strlen = (len(nm) + 4) & (~3) - def __str__(self): - return "%s %s %d" % (self.addr, self.name, self.strlen) - def __repr__(self): - return "%s %s %d" % (self.addr, self.name, self.strlen) - - -structDef = ">LLLL" - -symNames = [] - - -proc = subprocess.Popen(["nm", "-S", sys.argv[1]], stdout=subprocess.PIPE) - -symbols = proc.communicate()[0].decode('ascii').split("\n") -for line in symbols: - # format: - # 80153210 000000f8 T global_sym - # 80153210 t static_sym - tokens = line.split() - if len(tokens) >= 3 and len(tokens[-2]) == 1: - addr = int(tokens[0], 16) - if addr & 0x80000000 and tokens[-2].lower() == "t": - symNames.append(MapEntry(tokens[-1], addr)) - - - -f1 = open(sys.argv[2], "wb+") -f2 = open(sys.argv[3], "wb+") - -symNames.sort(key=lambda x: x.addr) - -off = 0 -for x in symNames: - f1.write(struct.pack(structDef, x.addr, off, len(x.name), 0)) - f2.write(struct.pack(">%ds" % x.strlen, bytes(x.name, encoding="ascii"))) - off += x.strlen - - -f1.close() -f2.close() - -# print('\n'.join([str(hex(x.addr)) + " " + x.name for x in symNames])) - diff --git a/tools/n64sym.c b/tools/n64sym.c new file mode 100644 index 0000000000..1d01256cc5 --- /dev/null +++ b/tools/n64sym.c @@ -0,0 +1,562 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "common/subprocess.h" +#include "common/polyfill.h" +#include "common/utils.h" +#include "common/binout.h" + +#include "common/binout.c" + +bool flag_verbose = false; +int flag_max_sym_len = 64; +bool flag_inlines = true; +bool flag_all_lines = false; +const char *n64_inst = NULL; +const char *addr2line_exe = NULL; +const char *objdump_exe = NULL; + +// Printf if verbose +void verbose(const char *fmt, ...) { + if (flag_verbose) { + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + } +} + +void usage(const char *progname) +{ + fprintf(stderr, "%s - Prepare symbol table for N64 ROMs\n", progname); + fprintf(stderr, "\n"); + fprintf(stderr, "Usage: %s [flags] []\n", progname); + fprintf(stderr, "\n"); + fprintf(stderr, "Command-line flags:\n"); + fprintf(stderr, " -j/--objdump Path to the objdump exe\n"); + fprintf(stderr, " -c/--addr2line Path to the addr2line exe\n"); + fprintf(stderr, " -v/--verbose Verbose output\n"); + fprintf(stderr, " -m/--max-len Maximum symbol length (default: 64)\n"); + fprintf(stderr, " -a/--all-lines Generate full address->line info (Can take seconds on its own)\n"); + fprintf(stderr, " --no-inlines Do not export inlined symbols\n"); + fprintf(stderr, "\n"); + // fprintf(stderr, "This program requires a libdragon toolchain installed in $N64_INST.\n"); +} + +char *stringtable = NULL; +struct { char *key; int value; } *string_hash = NULL; +// For non-callsites, make sure only one entry per line gets in +struct { char *key; int value; } *linedb = NULL; + +int stringtable_add(char *word) +{ + if (!string_hash) { + stbds_sh_new_arena(string_hash); + stbds_shdefault(string_hash, -1); + } + + int word_len = strlen(word); + if (stringtable) { + int pos = stbds_shget(string_hash, word); + if (pos >= 0) + return pos; + } + + // Append the word (without the trailing \0) + int idx = stbds_arraddnindex(stringtable, word_len); + memcpy(stringtable + idx, word, word_len); + + // Add all prefixes to the hash + for (int i = word_len; i >= 2; --i) { + char ch = word[i]; + word[i] = 0; + stbds_shput(string_hash, word, idx); + word[i] = ch; + } + return idx; +} + +struct symtable_s { + uint32_t uuid; + uint32_t addr; + char *func; + char *file; + int line; + + int func_sidx; + int file_sidx; + + int func_offset; + + bool is_func, is_inline; +} *symtable = NULL; + +void symbol_add(const char *elf, uint32_t addr, bool is_func) +{ + // We keep one addr2line process open for the last ELF file we processed. + // This allows to convert multiple symbols very fast, avoiding spawning a + // new process for each symbol. + // NOTE: we cannot use popen() here because on some platforms (eg. glibc) + // it only allows a single direction pipe, and we need both directions. + // So we rely on the subprocess library for this. + static char *addrbin = NULL; + static struct subprocess_s subp; + static FILE *addr2line_w = NULL, *addr2line_r = NULL; + static const char *cur_elf = NULL; + static char *line_buf = NULL; + static size_t line_buf_size = 0; + + // Check if this is a new ELF file (or it's the first time we run this function) + if (!cur_elf || strcmp(cur_elf, elf)) { + if (cur_elf) { + subprocess_terminate(&subp); + cur_elf = NULL; addr2line_r = addr2line_w = NULL; + } + if (!addrbin) + asprintf(&addrbin, "%s", addr2line_exe); + + const char *cmd_addr[16] = {0}; int i = 0; + cmd_addr[i++] = addrbin; + cmd_addr[i++] = "--addresses"; + cmd_addr[i++] = "--functions"; + cmd_addr[i++] = "--demangle"; + if (flag_inlines) cmd_addr[i++] = "--inlines"; + cmd_addr[i++] = "--exe"; + cmd_addr[i++] = elf; + + if (subprocess_create(cmd_addr, subprocess_option_search_user_path | subprocess_option_inherit_environment | subprocess_option_no_window, &subp) != 0) { + fprintf(stderr, "[symbol_add] Error: cannot run: %s\n", addrbin); + exit(1); + } + addr2line_w = subprocess_stdin(&subp); + addr2line_r = subprocess_stdout(&subp); + cur_elf = elf; + } + + // Send the address to addr2line and fetch back the symbol and the function name + // Since we activated the "--inlines" option, addr2line produces an unknown number + // of output lines. This is a problem with pipes, as we don't know when to stop. + // Thus, we always add a dummy second address (0xffffffff) so that we stop when we see the + // reply for it. NOTE: we can't use 0x0 as dummy address as DSOs are partially + // linked so there are really symbols at 0. + fprintf(addr2line_w, "%08x\n0xffffffff\n", addr); + fflush(addr2line_w); + + // First line is the address. It's just an echo, so ignore it. + int n = getline(&line_buf, &line_buf_size, addr2line_r); + assert(n >= 2 && strncmp(line_buf, "0x", 2) == 0); + + // Add one symbol for each inlined function + bool at_least_one = false; + while (1) { + // First line is the function name. If instead it's the dummy 0x0 address, + // it means that we're done. + int n = getline(&line_buf, &line_buf_size, addr2line_r); + if (strncmp(line_buf, "0xffffffff", 10) == 0) break; + n--; + if (line_buf[n-1] == '\r') n--; // Remove trailing \r (Windows) + + // If the function of name is longer than 64 bytes, truncate it. This also + // avoid paradoxically long function names like in C++ that can even be + // several thousands of characters long. + char *func = strndup(line_buf, MIN(n, flag_max_sym_len)); + if (n > flag_max_sym_len) strcpy(&func[flag_max_sym_len-3], "..."); + + // Second line is the file name and line number + int ret = getline(&line_buf, &line_buf_size, addr2line_r); + assert(ret != -1); + char *colon = strrchr(line_buf, ':'); + char *file = strndup(line_buf, colon - line_buf); + char *backup = file; + file = strstr(file, "src/"); + if (file == NULL) { + file = strstr(backup, "asm/"); + if (file == NULL) { + file = backup; + } + } + int line = atoi(colon + 1); + + // Add the callsite to the list + stbds_arrput(symtable, ((struct symtable_s) { + .uuid = stbds_arrlen(symtable), + .addr = addr, + .func = func, + .file = file, + .line = line, + .is_func = is_func, + .is_inline = true, + })); + at_least_one = true; + } + assert(at_least_one); + symtable[stbds_arrlen(symtable)-1].is_inline = false; + + // Read and skip the two remaining lines (function and file position) + // that refers to the dummy 0x0 address + getline(&line_buf, &line_buf_size, addr2line_r); + getline(&line_buf, &line_buf_size, addr2line_r); +} + +void address_add(const char *elf, uint32_t addr) { + // We keep one addr2line process open for the last ELF file we processed. + // This allows to convert multiple symbols very fast, avoiding spawning a + // new process for each symbol. + // NOTE: we cannot use popen() here because on some platforms (eg. glibc) + // it only allows a single direction pipe, and we need both directions. + // So we rely on the subprocess library for this. + static char *addrbin = NULL; + static struct subprocess_s subp; + static FILE *addr2line_w = NULL, *addr2line_r = NULL; + static const char *cur_elf = NULL; + static char *line_buf = NULL; + static size_t line_buf_size = 0; + + // Check if this is a new ELF file (or it's the first time we run this function) + if (!cur_elf || strcmp(cur_elf, elf)) { + if (cur_elf) { + subprocess_terminate(&subp); + cur_elf = NULL; addr2line_r = addr2line_w = NULL; + } + if (!addrbin) + asprintf(&addrbin, "%s", addr2line_exe); + + const char *cmd_addr[16] = {0}; int i = 0; + cmd_addr[i++] = addrbin; + cmd_addr[i++] = "--addresses"; + cmd_addr[i++] = "--functions"; + cmd_addr[i++] = "--demangle"; + if (flag_inlines) cmd_addr[i++] = "--inlines"; + cmd_addr[i++] = "--exe"; + cmd_addr[i++] = elf; + + if (subprocess_create(cmd_addr, subprocess_option_search_user_path | subprocess_option_inherit_environment | subprocess_option_no_window, &subp) != 0) { + fprintf(stderr, "[address_add] Error: cannot run: %s\n", addrbin); + exit(1); + } + addr2line_w = subprocess_stdin(&subp); + addr2line_r = subprocess_stdout(&subp); + cur_elf = elf; + } + + // Send the address to addr2line and fetch back the symbol and the function name + // Since we activated the "--inlines" option, addr2line produces an unknown number + // of output lines. This is a problem with pipes, as we don't know when to stop. + // Thus, we always add a dummy second address (0xffffffff) so that we stop when we see the + // reply for it. NOTE: we can't use 0x0 as dummy address as DSOs are partially + // linked so there are really symbols at 0. + fprintf(addr2line_w, "%08x\n0xffffffff\n", addr); + fflush(addr2line_w); + + // First line is the address. It's just an echo, so ignore it. + int n = getline(&line_buf, &line_buf_size, addr2line_r); + assert(n >= 2 && strncmp(line_buf, "0x", 2) == 0); + + // Add one symbol for each inlined function + bool at_least_one = false; + while (1) { + // First line is the function name. If instead it's the dummy 0x0 address, + // it means that we're done. + int n = getline(&line_buf, &line_buf_size, addr2line_r); + if (strncmp(line_buf, "0xffffffff", 10) == 0) break; + n--; + if (line_buf[n-1] == '\r') n--; // Remove trailing \r (Windows) + + // If the function of name is longer than 64 bytes, truncate it. This also + // avoid paradoxically long function names like in C++ that can even be + // several thousands of characters long. + char *func = strndup(line_buf, MIN(n, flag_max_sym_len)); + if (n > flag_max_sym_len) strcpy(&func[flag_max_sym_len-3], "..."); + + // Second line is the file name and line number + int ret = getline(&line_buf, &line_buf_size, addr2line_r); + assert(ret != -1); + char *colon = strrchr(line_buf, ':'); + char *file = strndup(line_buf, colon - line_buf); + char *backup = file; + file = strstr(file, "src/"); + if (file == NULL) { + file = strstr(backup, "asm/"); + if (file == NULL) { + file = backup; + } + } + int line = atoi(colon + 1); + + char fileline_key[PATH_MAX]; + sprintf(fileline_key, "%s:%d", file, line); + + // Add the addr to the list, only if that line entry doesn't already exist + if (stbds_shgetp_null(linedb, fileline_key) == NULL) { + stbds_arrput(symtable, ((struct symtable_s) { + .uuid = stbds_arrlen(symtable), + .addr = addr, + .func = func, + .file = file, + .line = line, + .is_func = false, + .is_inline = true, + })); + + stbds_shput(linedb, fileline_key, line); + } + at_least_one = true; + } + assert(at_least_one); + symtable[stbds_arrlen(symtable)-1].is_inline = false; + + // Read and skip the two remaining lines (function and file position) + // that refers to the dummy 0x0 address + getline(&line_buf, &line_buf_size, addr2line_r); + getline(&line_buf, &line_buf_size, addr2line_r); +} + +bool elf_find_callsites(const char *elf) +{ + // Start objdump to parse the disassembly of the ELF file + char *cmd = NULL; + asprintf(&cmd, "%s -d %s", objdump_exe, elf); + verbose("Running: %s\n", cmd); + FILE *disasm = popen(cmd, "r"); + if (!disasm) { + fprintf(stderr, "[elf_find_callsites] Error: cannot run: %s\n", cmd); + exit(1); + } + + // Parse the disassembly + char *line = NULL; size_t line_size = 0; + while (getline(&line, &line_size, disasm) != -1) { + // Find the functions + // format: + // 807fe800 : + if (strstr(line, ">:")) { + uint32_t addr = strtoul(line, NULL, 16); + // Prevent segmented addresses for now + if ((addr & 0xFF000000) == 0x80000000) { + symbol_add(elf, addr, true); + } + } + // Find the callsites + if (strstr(line, "\tjal\t") || strstr(line, "\tjalr\t") || strstr(line, "\tsyscall")) { + uint32_t addr = strtoul(line, NULL, 16); + // Prevent segmented addresses for now + if ((addr & 0xFF000000) == 0x80000000) { + symbol_add(elf, addr, false); + } + } + + if (flag_all_lines) { + if (line[8] == ':') { + uint32_t addr = strtoul(line, NULL, 16); + // Prevent segmented addresses for now + if ((addr & 0xFF000000) == 0x80000000) { + address_add(elf, addr); + } + } + } + } + free(line); + free(cmd); + int status = pclose(disasm); +#ifdef __MINGW32__ + return status == 0; +#else + return WIFEXITED(status) && WEXITSTATUS(status) == 0; +#endif +} + +void compute_function_offsets(void) +{ + uint32_t func_addr = 0; + for (int i=0; iis_func) { + func_addr = s->addr; + s->func_offset = 0; + } else { + s->func_offset = s->addr - func_addr; + } + } +} + +int symtable_sort_by_addr(const void *a, const void *b) +{ + const struct symtable_s *sa = a; + const struct symtable_s *sb = b; + // In case the address match, it means that there are multiple + // inlines at this address. Sort by insertion order (aka stable sort) + // so that we preserve the inline order. + if (sa->addr != sb->addr) + return sa->addr - sb->addr; + return sa->uuid - sb->uuid; +} + +int symtable_sort_by_func(const void *a, const void *b) +{ + const struct symtable_s *sa = a; + const struct symtable_s *sb = b; + int sa_len = sa->func ? strlen(sa->func) : 0; + int sb_len = sb->func ? strlen(sb->func) : 0; + return sb_len - sa_len; +} + +void process(const char *infn, const char *outfn) +{ + verbose("Processing: %s -> %s\n", infn, outfn); + + // First, find all functions and call sites. We do this by disassembling + // the ELF file and grepping it. + if (!elf_find_callsites(infn)) { + fprintf(stderr, "Error: objdump failed\n"); + exit(1); + } + verbose("Found %d callsites\n", stbds_arrlen(symtable)); + + // Sort the symbole table by symbol length. We want longer symbols + // to go in first, so that shorter symbols can be found as substrings. + // We sort by function name rather than file name, because we expect + // substrings to match more in functions. + verbose("Sorting symbol table...\n"); + qsort(symtable, stbds_arrlen(symtable), sizeof(struct symtable_s), symtable_sort_by_func); + + // Go through the symbol table and build the string table + verbose("Creating string table...\n"); + for (int i=0; i < stbds_arrlen(symtable); i++) { + if (i % 5000 == 0) + verbose(" %d/%d\n", i, stbds_arrlen(symtable)); + struct symtable_s *sym = &symtable[i]; + if (sym->func) + sym->func_sidx = stringtable_add(sym->func); + else + sym->func_sidx = -1; + if (sym->file) + sym->file_sidx = stringtable_add(sym->file); + else + sym->file_sidx = -1; + } + + // Sort the symbol table by address + qsort(symtable, stbds_arrlen(symtable), sizeof(struct symtable_s), symtable_sort_by_addr); + + // Fill in the function offset field in the entries in the symbol table. + verbose("Computing function offsets...\n"); + compute_function_offsets(); + + // Write the symbol table to file + verbose("Writing %s\n", outfn); + FILE *out = fopen(outfn, "wb"); + if (!out) { + fprintf(stderr, "Cannot create file: symtable.bin\n"); + exit(1); + } + + // Write header. See symtable_header_t in backtrace.c for the layout. + fwrite("SYMT", 4, 1, out); + w32(out, 2); // Version + int addrtable_off = w32_placeholder(out); + w32(out, stbds_arrlen(symtable)); + int symtable_off = w32_placeholder(out); + w32(out, stbds_arrlen(symtable)); + int stringtable_off = w32_placeholder(out); + w32(out, stbds_arrlen(stringtable)); + + // Write address table. This is a sequence of 32-bit addresses. + walign(out, 16); + w32_at(out, addrtable_off, ftell(out)); + for (int i=0; i < stbds_arrlen(symtable); i++) { + struct symtable_s *sym = &symtable[i]; + w32(out, sym->addr | (sym->is_func ? 0x1 : 0) | (sym->is_inline ? 0x2 : 0)); + } + + // Write symbol table. See symtable_entry_t in backtrace.c for the layout. + walign(out, 16); + w32_at(out, symtable_off, ftell(out)); + for (int i=0; i < stbds_arrlen(symtable); i++) { + struct symtable_s *sym = &symtable[i]; + w32(out, sym->func_sidx); + w32(out, sym->file_sidx); + w16(out, strlen(sym->func)); + w16(out, strlen(sym->file)); + w16(out, (uint16_t)(sym->line < 65536 ? sym->line : 0)); + w16(out, (uint16_t)(sym->func_offset < 0x10000 ? sym->func_offset : 0)); + } + + walign(out, 16); + w32_at(out, stringtable_off, ftell(out)); + fwrite(stringtable, stbds_arrlen(stringtable), 1, out); + fclose(out); +} + +int main(int argc, char *argv[]) +{ + const char *outfn = NULL; + + int i; + for (i = 1; i < argc && argv[i][0] == '-'; i++) { + if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { + usage(argv[0]); + return 0; + } else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")) { + flag_verbose = true; + } else if (!strcmp(argv[i], "--no-inlines")) { + flag_inlines = false; + } else if (!strcmp(argv[i], "-a") || !strcmp(argv[i], "--all-lines")) { + flag_all_lines = true; + } else if (!strcmp(argv[i], "-o") || !strcmp(argv[i], "--output")) { + if (++i == argc) { + fprintf(stderr, "missing argument for %s\n", argv[i-1]); + return 1; + } + outfn = argv[i]; + } else if (!strcmp(argv[i], "-m") || !strcmp(argv[i], "--max-len")) { + if (++i == argc) { + fprintf(stderr, "missing argument for %s\n", argv[i-1]); + return 1; + } + flag_max_sym_len = atoi(argv[i]); + } else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--addr2line")) { + if (++i == argc) { + fprintf(stderr, "missing argument for %s\n", argv[i-1]); + return 1; + } + addr2line_exe = argv[i]; + } else if (!strcmp(argv[i], "-j") || !strcmp(argv[i], "--objdump")) { + if (++i == argc) { + fprintf(stderr, "missing argument for %s\n", argv[i-1]); + return 1; + } + objdump_exe = argv[i]; + } else { + fprintf(stderr, "invalid flag: %s\n", argv[i]); + return 1; + } + } + + if (i == argc) { + fprintf(stderr, "missing input filename\n"); + return 1; + } + + const char *infn = argv[i]; + if (i < argc-1) + outfn = argv[i+1]; + else + outfn = change_ext(infn, ".sym"); + + // Check that infn exists and is readable + FILE *in = fopen(infn, "rb"); + if (!in) { + fprintf(stderr, "Error: cannot open file: %s\n", infn); + return 1; + } + fclose(in); + + process(infn, outfn); + return 0; +} +