├── .clangd ├── .editorconfig ├── .github └── workflows │ ├── cbundle.yml │ └── convsym.yml ├── LICENSE ├── Makefile ├── README.md ├── build └── .gitignore ├── modules ├── .gitignore ├── core │ ├── 1bpp_Decompress.asm │ ├── Console.asm │ ├── Console.defs.asm │ ├── Format_String.asm │ ├── Format_String.defs.asm │ ├── Formatter_Bin.asm │ ├── Formatter_Dec.asm │ ├── Formatter_Hex.asm │ ├── Formatter_Sym.asm │ ├── KDebug.asm │ ├── KDebug.defs.asm │ ├── Macros.asm │ └── Symbols.asm ├── errorhandler-core │ ├── Debugger_AddressRegisters.asm │ ├── Debugger_Backtrace.asm │ ├── ErrorHandler.asm │ ├── Exceptions.asm │ ├── Extensions.asm │ ├── Font.asm │ ├── Main.asm │ ├── Makefile │ ├── Makefile.win │ ├── inject-tables │ │ ├── ErrorHandler.Blob.txt │ │ ├── ErrorHandler.ExtSymbols.Blob.txt │ │ └── ErrorHandler.GAS.Blob.txt │ └── tests │ │ ├── .Makefile │ │ ├── .Makefile.win │ │ ├── FormatString.asm │ │ ├── FormatString_DummySymbols.log │ │ ├── FullException.asm │ │ └── GuessCaller.asm ├── errorhandler │ ├── BUILD.md │ ├── Debugger.Config.asm │ ├── Debugger.Constants.asm │ ├── Debugger.Macros.AS.asm │ ├── Debugger.Macros.ASM68K.asm │ ├── Debugger.asm │ ├── ErrorHandler-GAS.asm │ ├── ErrorHandler.Exceptions.asm │ ├── ErrorHandler.asm │ ├── Makefile │ ├── Makefile.win │ ├── README.md │ ├── docs │ │ ├── .images │ │ │ ├── blastem-win-console.png │ │ │ ├── eh_address_error_v.2.6.png │ │ │ ├── eh_address_regs.png │ │ │ ├── eh_assertion_v.2.6.png │ │ │ ├── eh_backtrace.png │ │ │ ├── eh_custom_debugger.png │ │ │ ├── eh_illegal_instr.png │ │ │ ├── eh_kdebug.png │ │ │ ├── gens-kmod_debug.png │ │ │ ├── gens-kmod_menu.png │ │ │ └── gens-kmod_messages.png │ │ ├── Debug_macros.md │ │ ├── Formatted_strings.md │ │ ├── how-to │ │ │ ├── Debugging_techniques.md │ │ │ ├── Modify_exception_header.md │ │ │ ├── Troubleshoot.md │ │ │ └── Use_KDebug_integration.md │ │ └── installation │ │ │ ├── Sonic_1_Github_AS.md │ │ │ ├── Sonic_1_Github_ASM68K.md │ │ │ ├── Sonic_1_Github_AS_old_2022.md │ │ │ ├── Sonic_1_Hivebrain_2005.md │ │ │ ├── Sonic_1_Hivebrain_2022.md │ │ │ └── Sonic_2_Github.md │ └── tests │ │ ├── .Makefile │ │ ├── .Makefile.win │ │ ├── addressing-modes.asm │ │ ├── asm68k-dot-compat.asm │ │ ├── assertions.asm │ │ ├── cbundle_disclaimer │ │ ├── console-run.asm │ │ ├── console-utils.asm │ │ ├── flow-test.asm │ │ ├── linkable.asm │ │ ├── raise-error.asm │ │ └── shadow-macros.asm ├── exec │ ├── as │ │ ├── as.msg │ │ ├── asw.exe │ │ ├── cmdarg.msg │ │ ├── ioerrs.msg │ │ ├── p2bin.exe │ │ ├── p2bin.msg │ │ └── tools.msg │ ├── asm68k.exe │ └── psylink.exe ├── mdshell-core │ ├── MDShell.asm │ ├── Makefile │ ├── Makefile.win │ └── inject-tables │ │ ├── MDShell.Headless.txt │ │ └── MDShell.txt └── mdshell │ ├── BUILD.md │ ├── MDShell.Headless.asm │ ├── MDShell.asm │ ├── Makefile │ ├── Makefile.win │ ├── README.md │ ├── docs │ ├── .images │ │ └── mdshell-hello-world.png │ ├── Formatted_strings.md │ └── Macros_reference.md │ └── tests │ ├── .Makefile │ ├── .Makefile.win │ ├── cbundle_disclaimer │ ├── flow-test.asm │ ├── hello-world.asm │ ├── linkable.asm │ └── raise-error.asm ├── pyrightconfig.json └── utils ├── .Makefile ├── .Makefile.win ├── blobtoasm ├── blobtoasm.py └── readme.txt ├── cbundle ├── Makefile ├── Makefile.win ├── readme.txt ├── src │ ├── main.cpp │ └── parser.cpp └── tests │ ├── .gitignore │ ├── input │ ├── sample-common-include.txt │ └── sample.cbundle │ ├── output-expected │ ├── sample-file-1.txt │ └── sample-file-2.txt │ ├── output │ └── .gitkeep │ └── test.py ├── convsym ├── CHANGES.md ├── Makefile ├── Makefile.win ├── README.md ├── docs │ └── DEB2_Format.txt ├── src │ ├── input │ │ ├── ASM68K_Listing.cpp │ │ ├── ASM68K_Sym.cpp │ │ ├── AS_Listing.cpp │ │ ├── AS_Listing_Experimental.cpp │ │ ├── InputWrapper.hpp │ │ ├── Log.cpp │ │ ├── TXT.cpp │ │ └── Wrappers.cpp │ ├── main.cpp │ ├── output │ │ ├── ASM.cpp │ │ ├── DEB1.cpp │ │ ├── DEB2.cpp │ │ ├── Log.cpp │ │ ├── OutputWrapper.hpp │ │ └── Wrappers.cpp │ └── util │ │ └── SymbolTable.hpp └── tests │ ├── .gitignore │ ├── input.tar.xz │ ├── output-expected.tar.xz │ ├── output │ └── .gitkeep │ └── test.py ├── lib-py ├── .gitignore └── test_framework.py └── lib ├── ArgvParser.hpp ├── BitStream.hpp ├── Huffman.hpp ├── IO.hpp ├── OptsParser.hpp └── utils.hpp /.clangd: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | Add: [-std=c++20, -Wall, -pedantic-errors, -I../../lib, -I../../../lib] 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | 3 | # EditorConfig is awesome: http://EditorConfig.org 4 | 5 | # top-most EditorConfig file 6 | root = true 7 | 8 | # Unix-style newlines with a newline ending every file 9 | [*] 10 | end_of_line = lf 11 | 12 | # 4 space indentation for source code files 13 | [*.{asm,c,cpp,h,hpp,py}] 14 | indent_style = tab 15 | indent_size = 4 16 | -------------------------------------------------------------------------------- /.github/workflows/cbundle.yml: -------------------------------------------------------------------------------- 1 | name: CBundle 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | paths: [ "utils/core/**", "utils/core-py/**", "utils/cbundle/**" ] 7 | pull_request: 8 | branches: [ "master" ] 9 | paths: [ "utils/core/**", "utils/core-py/**", "utils/cbundle/**" ] 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Build 18 | run: cd utils/cbundle && make 19 | - name: Test 20 | run: cd utils/cbundle && make test 21 | -------------------------------------------------------------------------------- /.github/workflows/convsym.yml: -------------------------------------------------------------------------------- 1 | name: ConvSym 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | paths: [ "utils/core/**", "utils/core-py/**", "utils/convsym/**" ] 7 | pull_request: 8 | branches: [ "master" ] 9 | paths: [ "utils/core/**", "utils/core-py/**", "utils/convsym/**" ] 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Build 18 | run: cd utils/convsym && make 19 | - name: Test 20 | run: cd utils/convsym && make test 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 vladikcomper 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | .PHONY: all utils modules test clean 4 | 5 | MAKEFILE := Makefile 6 | ifeq ($(OS),Windows_NT) 7 | MAKEFILE := Makefile.win 8 | endif 9 | 10 | all: utils modules 11 | 12 | utils: 13 | $(MAKE) -C utils/convsym -f $(MAKEFILE) 14 | $(MAKE) -C utils/cbundle -f $(MAKEFILE) 15 | 16 | modules: 17 | $(MAKE) -C modules/mdshell -f $(MAKEFILE) 18 | $(MAKE) -C modules/errorhandler -f $(MAKEFILE) 19 | 20 | test: all 21 | $(MAKE) -C utils/convsym test -f $(MAKEFILE) 22 | $(MAKE) -C utils/cbundle test -f $(MAKEFILE) 23 | $(MAKE) -C modules/mdshell tests -f $(MAKEFILE) 24 | $(MAKE) -C modules/errorhandler tests -f $(MAKEFILE) 25 | $(MAKE) -C modules/errorhandler-core tests -f $(MAKEFILE) 26 | 27 | clean: 28 | ifeq ($(OS),Windows_NT) 29 | -rd /q /s build\modules build\utils 30 | else 31 | rm -drf build/modules build/utils 32 | endif 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MD-Modules 2 | 3 | Various debugging modules and utilities for Sega Mega-Drive / Genesis ROMs. 4 | 5 | ## Repository structure 6 | 7 | This repository includes modules for Mega-Drive projects (`modules/` directory) and various utilities (`utils/` directory) in one place, since most of them are tightly integrated or depend on each other. See below for the lists of modules and utilities. 8 | 9 | ### Modules 10 | 11 | * [MD Debugger and Error Handler](modules/errorhandler) - also known as _The Advanced Error Handler and Debugger_, handles exceptions and helps to debug your Mega-Drive ROMs in any emulators and on the real hardware; 12 | * [MD-Shell](modules/mdshell) - a stand-alone easy to use "assembly header" to run small programs as Mega-Drive ROMs, ncludes MD Debugger. 13 | 14 | ### Utilities 15 | 16 | * [ConvSym](utils/convsym) _(C++20)_ - a symbol extraction and conversion utility; 17 | * [CBundle](utils/cbundle) _(C++20)_ - a custom pre-processor used to build debugger's bundles from the shared "cross-assembler" source files; 18 | * [BlobToAsm](utils/blobtoasm) _(Python 3.8+)_ - a utility to render binary files in M68K assembly with additional tricks (e.g. offset-based expression injection); 19 | 20 | ## Building from source code 21 | 22 | This repository aims to be cross-platform, designed with Linux, Windows, MacOS (and other BSD systems) in mind. It makes extensive use of GNU-flavored Makefiles for both \*nix and Windows systems (see `Makefile` for \*nix and `Makefile.win` for Windows). Please read notes below to make sure you have all the prerequisites and your platform is fully supported. 23 | 24 | ### Dependencies 25 | 26 | - *GNU Make* is required to build pretty much everything. 27 | 28 | - **Utilities** require a *GCC* or *Clang* compiler with C++20 support and *Python 3.8* or newer. 29 | 30 | - **Modules** fully depend on *utilities*, so they require all the dependencies listed above. Non-Windows systems also require *Wine* to run assemblers (they're 32-bit Windows executables). 31 | 32 | ### Windows 33 | 34 | > [!NOTE] 35 | > 36 | > Only Windows 7 or newer is supported because of Python 3.8 and C++20 requirements. This guide targets Windows 10 or later; for Windows 7 you need alternative ways to install dependencies which are not covered here. 37 | 38 | Make sure you have all the dependencies. This example uses Chocolatey to automate dependency installation, but you may choose any other option that works for you: 39 | 40 | ```sh 41 | choco install mingw python3 make 42 | ``` 43 | 44 | Once dependencies are installed, build process is the same as on Unix-like systems. In Command Prompt (`cmd.exe`), use one of the following commands: 45 | 46 | ```sh 47 | make 48 | # or separately: 49 | make utils 50 | make modules 51 | ``` 52 | 53 | > [!NOTE] 54 | > 55 | > You must have `make.exe`, `gcc.exe`, `python3.exe` and a few others available via `PATH` environment variable for all commands to work properly. Chocolatey and other package managers usually take care of that, but if you get "XXX is not recognized as an internal or external command ..." errors, then your shell cannot locate those executables, so you have to find their installation paths and append to the `PATH` variable manually. 56 | 57 | If you want to invoke `make` from individual directories however (not root), be sure to use `make -f Makefile.win` instead (the root Makefile does it automatically). 58 | 59 | ### Linux 60 | 61 | Make sure you have the necessary dependencies using your package manager (this following example uses `apt` under Debian/Ubuntu): 62 | 63 | ```sh 64 | apt install g++ make python3 wine 65 | ``` 66 | 67 | To build everything, use one of the following commands: 68 | 69 | ```sh 70 | make 71 | # or separately: 72 | make utils 73 | make modules 74 | ``` 75 | 76 | ### FreeBSD 77 | 78 | Make sure you have the necessary dependencies: 79 | 80 | ```sh 81 | pkg install gmake python3 wine 82 | ``` 83 | 84 | > [!NOTE]\ 85 | > If you're running a 64-bit system, you'll likely also need a 32-bit installation of Wine. As of FreeBSD 13, the following script may automate the process: 86 | > 87 | > ```sh 88 | > /usr/local/share/wine/pkg32.sh install wine 89 | > ``` 90 | > 91 | > Consult your distribution's manuals for more information. 92 | 93 | Please note that you specifically need to use GNU-Make instead of BSD-flavoured Make everywhere: 94 | 95 | ```sh 96 | gmake 97 | # or separately: 98 | gmake utils 99 | gmake modules 100 | ``` 101 | 102 | ### MacOS 103 | 104 | > [!WARNING]\ 105 | > Since MacOS Catalina 10.15, 32-bit software is no longer supported. You may not be able to build *Modules* which require 32-bit Wine. The only proper workaround (at the time of writing) is to use a VM. 106 | 107 | Make sure you have Wine: 108 | 109 | ```sh 110 | brew tap homebrew/cask-versions 111 | brew install --cask --no-quarantine wine-stable 112 | ``` 113 | 114 | To build everything, use one of the following commands: 115 | 116 | ```sh 117 | make 118 | # or separately: 119 | make utils 120 | make modules 121 | ``` 122 | -------------------------------------------------------------------------------- /build/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | **/* 3 | !.gitignore -------------------------------------------------------------------------------- /modules/.gitignore: -------------------------------------------------------------------------------- 1 | *.lst 2 | *.gen 3 | *.bin 4 | *.sym 5 | -------------------------------------------------------------------------------- /modules/core/1bpp_Decompress.asm: -------------------------------------------------------------------------------- 1 | 2 | ; =============================================================== 3 | ; --------------------------------------------------------------- 4 | ; Error handling and debugging modules 5 | ; 6 | ; (c) 2016-2023, Vladikcomper 7 | ; --------------------------------------------------------------- 8 | ; Fast 1bpp decompressor 9 | ; --------------------------------------------------------------- 10 | ; INPUT: 11 | ; a0 Source 1bpp art 12 | ; a1 Decode table (generated or manual) 13 | ; d4 .w Size of art in bytes - 1 14 | ; a6 VDP Data Port 15 | ; 16 | ; USES: 17 | ; a0, d0-d2/d4 18 | ; --------------------------------------------------------------- 19 | 20 | Decomp1bpp: __global 21 | moveq #$1E, d2 22 | 23 | @row: 24 | move.b (a0)+, d0 ; d0 = %aaaa bbbb 25 | move.b d0, d1 26 | lsr.b #3, d1 ; d1 = %000a aaab 27 | and.w d2, d1 ; d1 = %000a aaa0 28 | move.w (a1,d1), (a6) ; decompress first nibble 29 | 30 | add.b d0, d0 ; d0 = %aaab bbb0 31 | and.w d2, d0 ; d0 = %000b bbb0 32 | move.w (a1,d0), (a6) ; decompress second nibble 33 | 34 | dbf d4, @row 35 | 36 | rts 37 | -------------------------------------------------------------------------------- /modules/core/Console.defs.asm: -------------------------------------------------------------------------------- 1 | if def(_CONSOLE_DEFS)=0 2 | _CONSOLE_DEFS: equ 1 3 | 4 | ; =============================================================== 5 | ; --------------------------------------------------------------- 6 | ; Error handling and debugging modules 7 | ; 8 | ; (c) 2016-2023, Vladikcomper 9 | ; --------------------------------------------------------------- 10 | ; Console Module (definitions only) 11 | ; --------------------------------------------------------------- 12 | 13 | ; --------------------------------------------------------------- 14 | ; RAM structure 15 | ; --------------------------------------------------------------- 16 | 17 | rsreset 18 | Console_RAM equ __rs 19 | Console.ScreenPosReq rs.l 1 ; current on-screen position request for VDP 20 | Console.ScreenRowReq: rs.l 1 ; start of row position request for VDP 21 | Console.CharsPerLine rs.w 1 ; d2 number of characters per line 22 | Console.CharsRemaining rs.w 1 ; d3 remaining number of characters 23 | Console.BasePattern rs.w 1 ; d4 base pattern 24 | Console.ScreenRowSz rs.w 1 ; d6 row size within screen position 25 | Console_RAM.size equ __rs-Console_RAM 26 | 27 | ; --------------------------------------------------------------- 28 | ; Constants 29 | ; --------------------------------------------------------------- 30 | 31 | ; Drawing flags supported in strings 32 | _newl equ $E0 33 | _cr equ $E6 34 | _pal0 equ $E8 35 | _pal1 equ $EA 36 | _pal2 equ $EC 37 | _pal3 equ $EE 38 | 39 | _setw equ $F0 40 | _setoff equ $F4 41 | _setpat equ $F8 42 | _setx equ $FA 43 | 44 | _ConsolePtrMagic: equ $5D 45 | 46 | ; Default size of a text buffer used by `FormatString`, allocated 47 | ; on the stack. 48 | ; MD Debugger uses a smaller buffer, because the stack is usually 49 | ; quite busy by the time exception is thrown. 50 | if def(__CONSOLE_TEXT_BUFFER_SIZE__)=0 51 | __CONSOLE_TEXT_BUFFER_SIZE__: equ $30 52 | endif 53 | 54 | ; --------------------------------------------------------------- 55 | ; Macros 56 | ; --------------------------------------------------------------- 57 | 58 | ; This macro checks if `ptrAReg` (e.g. `a3`) has contains a valid Console RAM pointer. 59 | ; The pointer is valid if its MSB contains the value of "_ConsolePtrMagic". 60 | ; This way we usually can test whether we're inside the Console or not. 61 | ; 62 | ; ARGUMENTS: 63 | ; ptrAReg - any of An registers that stores Console RAM pointer; 64 | ; scratchDReg - a Dn register that will be used for in-place calculations. 65 | 66 | Console_ChkRAMPointerValid: macro ptrAReg, scratchDReg 67 | move.l \ptrAReg, \scratchDReg 68 | swap \scratchDReg 69 | clr.b \scratchDReg 70 | cmp.w #_ConsolePtrMagic<<8, \scratchDReg ; is MSB of `usp` set to `_ConsolePtrMagic`? 71 | endm 72 | 73 | endif ; _CONSOLE_DEFS 74 | -------------------------------------------------------------------------------- /modules/core/Format_String.defs.asm: -------------------------------------------------------------------------------- 1 | if def(_FORMAT_STRING_DEFS)=0 2 | _FORMAT_STRING_DEFS: equ 1 3 | 4 | ; =============================================================== 5 | ; --------------------------------------------------------------- 6 | ; Error handling and debugging modules 7 | ; 8 | ; (c) 2016-2023, Vladikcomper 9 | ; --------------------------------------------------------------- 10 | ; String formatter module (definitions only) 11 | ; --------------------------------------------------------------- 12 | 13 | ; --------------------------------------------------------------- 14 | ; Constants 15 | ; --------------------------------------------------------------- 16 | 17 | _hex equ $80 18 | _dec equ $90 19 | _bin equ $A0 20 | _sym equ $B0 21 | _disp equ $C0 22 | _str equ $D0 23 | 24 | byte equ 0 25 | word equ 1 26 | long equ 3 27 | 28 | ; for number formatters ... 29 | signed equ 8 30 | 31 | ; for symbol formatters ... 32 | split equ 8 ; display symbol/offset only, don't draw displacement yet ... 33 | forced equ 4 ; display if symbol was not found 34 | 35 | ; for symbol displacement or offset formatters ... 36 | weak equ 8 ; don't draw offset (for use with _sym|forced, see above) 37 | 38 | ; --------------------------------------------------------------- 39 | __FSTRING_GenerateArgumentsCode & 40 | macro string 41 | 42 | __pos: set instr(\string,'%<') ; token position 43 | __stack:set 0 ; size of actual stack 44 | __sp: set 0 ; stack displacement 45 | 46 | ; Parse string itself 47 | while (__pos) 48 | 49 | ; Retrive expression in brackets following % char 50 | __endpos: set instr(__pos+1,\string,'>') 51 | __midpos: set instr(__pos+5,\string,' ') 52 | if (__midpos<1)|(__midpos>__endpos) 53 | __midpos: = __endpos 54 | endif 55 | __substr: substr __pos+1+1,__endpos-1,\string ; .type ea param 56 | __type: substr __pos+1+1,__pos+1+1+1,\string ; .type 57 | 58 | ; Expression is an effective address (e.g. %(.w d0 hex) ) 59 | if "\__type">>8="." 60 | __operand: substr __pos+1+1,__midpos-1,\string ; .type ea 61 | __param: substr __midpos+1,__endpos-1,\string ; param 62 | 63 | if "\__type"=".b" 64 | pushp "move\__operand\,1(sp)" 65 | pushp "subq.w #2, sp" 66 | __stack: = __stack+2 67 | __sp: = __sp+2 68 | 69 | elseif "\__type"=".w" 70 | pushp "move\__operand\,-(sp)" 71 | __stack: = __stack+1 72 | __sp: = __sp+2 73 | 74 | elseif "\__type"=".l" 75 | pushp "move\__operand\,-(sp)" 76 | __stack: = __stack+1 77 | __sp: = __sp+4 78 | 79 | else 80 | fatal 'Unrecognized type in string operand: %<\__substr>' 81 | endif 82 | endif 83 | 84 | __pos: set instr(__pos+1,\string,'%<') 85 | endw 86 | 87 | ; Generate stack code 88 | rept __stack 89 | popp __command 90 | \__command 91 | endr 92 | 93 | endm 94 | 95 | ; --------------------------------------------------------------- 96 | __FSTRING_GenerateDecodedString & 97 | macro string 98 | 99 | __lpos: set 1 ; start position 100 | __pos: set instr(\string,'%<') ; token position 101 | 102 | while (__pos) 103 | 104 | ; Write part of string before % token 105 | __substr: substr __lpos,__pos-1,\string 106 | dc.b "\__substr" 107 | 108 | ; Retrive expression in brakets following % char 109 | __endpos: set instr(__pos+1,\string,'>') 110 | __midpos: set instr(__pos+5,\string,' ') 111 | if (__midpos<1)|(__midpos>__endpos) 112 | __midpos: = __endpos 113 | endif 114 | __type: substr __pos+1+1,__pos+1+1+1,\string ; .type 115 | 116 | ; Expression is an effective address (e.g. %<.w d0 hex> ) 117 | if "\__type">>8="." 118 | __param: substr __midpos+1,__endpos-1,\string ; param 119 | 120 | ; Validate format setting ("param") 121 | if strlen("\__param")<1 122 | __param: substr ,,"hex" ; if param is ommited, set it to "hex" 123 | elseif strcmp("\__param","signed") 124 | __param: substr ,,"hex+signed" ; if param is "signed", correct it to "hex+signed" 125 | endif 126 | 127 | if (\__param < $80) 128 | inform 2,"Illegal operand format setting: ""\__param\"". Expected ""hex"", ""dec"", ""bin"", ""sym"", ""str"" or their derivatives." 129 | endif 130 | 131 | if "\__type"=".b" 132 | dc.b \__param 133 | elseif "\__type"=".w" 134 | dc.b \__param|1 135 | else 136 | dc.b \__param|3 137 | endif 138 | 139 | ; Expression is an inline constant (e.g. % ) 140 | else 141 | __substr: substr __pos+1+1,__endpos-1,\string 142 | dc.b \__substr 143 | endif 144 | 145 | __lpos: set __endpos+1 146 | __pos: set instr(__pos+1,\string,'%<') 147 | endw 148 | 149 | ; Write part of string before the end 150 | __substr: substr __lpos,,\string 151 | dc.b "\__substr" 152 | dc.b 0 153 | 154 | endm 155 | 156 | endc ; _FORMAT_STRING_DEFS -------------------------------------------------------------------------------- /modules/core/Formatter_Bin.asm: -------------------------------------------------------------------------------- 1 | 2 | ; =============================================================== 3 | ; --------------------------------------------------------------- 4 | ; Error handling and debugging modules 5 | ; 6 | ; (c) 2016-2023, Vladikcomper 7 | ; --------------------------------------------------------------- 8 | ; String formatters : Binary number 9 | ; --------------------------------------------------------------- 10 | ; INPUT: 11 | ; d1 Value 12 | ; 13 | ; d7 .w Number of bytes left in buffer, minus one 14 | ; a0 String buffer 15 | ; a4 Buffer flush function 16 | ; 17 | ; OUTPUT: 18 | ; (a0)++ ASCII characters for the converted value 19 | ; Carry=0 if buffer is not terminated, Carry=1 otherwise. 20 | ; 21 | ; WARNING! 22 | ; 1) Formatters can only use registers a3 / d0-d4 23 | ; 2) Formatters should decrement d7 after each symbol write. 24 | ; 3) When d7 decrements below 0, a buffer flush function 25 | ; loaded in a4 should be called. The standard function 26 | ; usually renders buffer's contents on the screen (see 27 | ; "Console_FlushBuffer"), then resets the buffer. 28 | ; This function will reload d7, a0 and Carry flag. 29 | ; 4) If Carry flag is set after calling buffer flush function, 30 | ; formatter should halt all further processing and return, 31 | ; retaining the returned Carry bit. 32 | ; --------------------------------------------------------------- 33 | 34 | FormatBin_Handlers: 35 | jmp FormatBin_Word(pc) ; $00 Word display handler 36 | ; --------------------------------------------------------------- 37 | jmp FormatBin_LongWord(pc) ; $04 Longword display handler 38 | ; --------------------------------------------------------------- 39 | ; jmp FormatBin_Byte(pc) ; $08 Byte display handler 40 | 41 | FormatBin_Byte: 42 | moveq #8-1, d2 43 | 44 | @loop: 45 | moveq #'0'/2,d0 46 | add.b d1,d1 47 | addx.b d0,d0 48 | move.b d0, (a0)+ 49 | 50 | dbf d7, @buffer_ok 51 | jsr (a4) 52 | bcs.s @quit 53 | @buffer_ok: 54 | 55 | dbf d2, @loop 56 | 57 | @quit: 58 | rts 59 | 60 | ; --------------------------------------------------------------- 61 | FormatBin_LongWord: 62 | swap d1 63 | bsr.s FormatBin_Word 64 | bcs.s FormatBin_Return 65 | swap d1 66 | 67 | FormatBin_Word: 68 | moveq #16-1, d2 69 | 70 | @loop: 71 | moveq #'0'/2,d0 72 | add.w d1,d1 73 | addx.b d0,d0 74 | move.b d0, (a0)+ 75 | 76 | dbf d7, @buffer_ok 77 | jsr (a4) 78 | bcs.s FormatBin_Return 79 | @buffer_ok: 80 | 81 | dbf d2, @loop 82 | 83 | FormatBin_Return: 84 | rts 85 | -------------------------------------------------------------------------------- /modules/core/Formatter_Dec.asm: -------------------------------------------------------------------------------- 1 | 2 | ; =============================================================== 3 | ; --------------------------------------------------------------- 4 | ; Error handling and debugging modules 5 | ; 6 | ; (c) 2016-2023, Vladikcomper 7 | ; --------------------------------------------------------------- 8 | ; String formatters : Decimal number 9 | ; --------------------------------------------------------------- 10 | ; INPUT: 11 | ; d1 Value 12 | ; 13 | ; d7 .w Number of bytes left in buffer, minus one 14 | ; a0 String buffer 15 | ; a4 Buffer flush function 16 | ; 17 | ; OUTPUT: 18 | ; (a0)++ ASCII characters for the converted value 19 | ; 20 | ; WARNING! 21 | ; 1) Formatters can only use registers a3 / d0-d4 22 | ; 2) Formatters should decrement d7 after each symbol write. 23 | ; 3) When d7 decrements below 0, a buffer flush function 24 | ; loaded in a4 should be called. The standard function 25 | ; usually renders buffer's contents on the screen (see 26 | ; "Console_FlushBuffer"), then resets the buffer. 27 | ; This function will reload d7, a0 and Carry flag. 28 | ; 4) If Carry flag is set after calling buffer flush function, 29 | ; formatter should halt all further processing and return, 30 | ; retaining the returned Carry bit. 31 | ; --------------------------------------------------------------- 32 | 33 | FormatDec_Handlers: 34 | jmp FormatDec_Word(pc) ; $00 Word display handler 35 | ; --------------------------------------------------------------- 36 | jmp FormatDec_LongWord(pc) ; $04 Longword display handler 37 | ; --------------------------------------------------------------- 38 | lea DecimalBase_Byte(pc), a3 ; $08 Byte display handler 39 | andi.w #$FF, d1 ; ... 40 | bra.s FormatDec ; ... 41 | 42 | ; --------------------------------------------------------------- 43 | FormatDec_Word: 44 | lea DecimalBase_Word(pc), a3 45 | 46 | FormatDec: 47 | clr.b d0 ; d0 will be trim zeros switcher 48 | moveq #9, d3 ; d3 will be DBcc iterator base 49 | move.w (a3)+, d4 ; d4 = decimal base 50 | 51 | FormatDec_Cont: 52 | @ProcessDigit: 53 | move.w d3, d2 54 | 55 | @FindDigit: 56 | sub.w d4, d1 57 | dbcs d2, @FindDigit 58 | 59 | add.w d4, d1 ; restore digit 60 | sub.w d3, d2 61 | neg.w d2 ; d2 = digit 62 | or.b d2, d0 ; have we met non-zero digit yet? 63 | beq.s @NextDigit ; if not, branch 64 | add.b #'0', d2 65 | move.b d2, (a0)+ 66 | 67 | dbf d7, @NextDigit 68 | jsr (a4) 69 | bcs.s FormatDec_Return 70 | 71 | @NextDigit: 72 | move.w (a3)+, d4 73 | bpl.s @ProcessDigit 74 | 75 | ; The last digit is done manually 76 | add.b #'0', d1 77 | move.b d1, (a0)+ 78 | dbf d7, FormatDec_Return 79 | jmp (a4) 80 | 81 | FormatDec_Return: 82 | rts 83 | 84 | ; --------------------------------------------------------------- 85 | FormatDec_LongWord: 86 | lea DecimalBase_Long(pc), a3 87 | clr.b d0 ; d0 will be trim zeros switcher 88 | moveq #9, d3 ; d3 will be DBcc iterator base 89 | move.l (a3)+, d4 ; d4 = decimal base 90 | 91 | @ProcessDigit: 92 | move.w d3, d2 93 | 94 | @FindDigit: 95 | sub.l d4, d1 96 | dbcs d2, @FindDigit 97 | 98 | add.l d4, d1 ; restore digit 99 | sub.w d3, d2 100 | neg.w d2 ; d2 = digit 101 | or.b d2, d0 ; have we met non-zero digit yet? 102 | beq.s @NextDigit ; if not, branch 103 | add.b #'0', d2 104 | move.b d2, (a0)+ 105 | 106 | dbf d7, @NextDigit 107 | jsr (a4) 108 | bcs.s FormatDec_Return 109 | 110 | @NextDigit: 111 | move.l (a3)+, d4 ; load next decimal base 112 | bpl.s @ProcessDigit ; if base is positive, branch 113 | ; otherwise, base is word-sized ... 114 | 115 | bra.s FormatDec_Cont ; continue drawing with word-sized version 116 | ; note that lower word of D4 already contains next decimal base ... 117 | 118 | ; --------------------------------------------------------------- 119 | DecimalBase_Long: 120 | dc.l 1000000000 121 | dc.l 100000000 122 | dc.l 10000000 123 | dc.l 1000000 124 | dc.l 100000 125 | dc.l 10000 126 | dc.w -1 ; marks switching between 'long' and 'word' modes 127 | dc.w 1000 128 | dc.w 100 129 | dc.w 10 130 | dc.w -1 ; marks end of digit searching 131 | 132 | ; --------------------------------------------------------------- 133 | DecimalBase_Word: 134 | dc.w 10000 135 | dc.w 1000 136 | 137 | DecimalBase_Byte: 138 | dc.w 100 139 | dc.w 10 140 | dc.w -1 ; marks end of digit searching 141 | -------------------------------------------------------------------------------- /modules/core/Formatter_Hex.asm: -------------------------------------------------------------------------------- 1 | 2 | ; =============================================================== 3 | ; --------------------------------------------------------------- 4 | ; Error handling and debugging modules 5 | ; 6 | ; (c) 2016-2023, Vladikcomper 7 | ; --------------------------------------------------------------- 8 | ; String formatters : Hexidecimal number 9 | ; --------------------------------------------------------------- 10 | ; INPUT: 11 | ; d1 Value 12 | ; 13 | ; d7 .w Number of bytes left in buffer, minus one 14 | ; a0 String buffer 15 | ; a4 Buffer flush function 16 | ; 17 | ; OUTPUT: 18 | ; (a0)++ ASCII characters for the converted value 19 | ; 20 | ; WARNING! 21 | ; 1) Formatters can only use registers a3 / d0-d4 22 | ; 2) Formatters should decrement d7 after each symbol write. 23 | ; 3) When d7 decrements below 0, a buffer flush function 24 | ; loaded in a4 should be called. The standard function 25 | ; usually renders buffer's contents on the screen (see 26 | ; "Console_FlushBuffer"), then resets the buffer. 27 | ; This function will reload d7, a0 and Carry flag. 28 | ; 4) If Carry flag is set after calling buffer flush function, 29 | ; formatter should halt all further processing and return, 30 | ; retaining the returned Carry bit. 31 | ; --------------------------------------------------------------- 32 | 33 | FormatHex_Handlers: 34 | jmp FormatHex_Word(pc) ; $00 ; handler for word 35 | ; --------------------------------------------------------------- 36 | jmp FormatHex_LongWord(pc) ; $04 ; handler for longword 37 | ; --------------------------------------------------------------- 38 | ; jmp FormatHex_Byte(pc) ; $08 ; handler for byte 39 | 40 | FormatHex_Byte: 41 | moveq #$F,d3 42 | move.w d1,d2 43 | lsr.w #4,d2 44 | and.w d3,d2 ; get nibble 45 | move.b HexDigitToChar(pc,d2), (a0)+ 46 | 47 | dbf d7, FormatHex_Word_WriteLastNibble2 48 | jsr (a4) 49 | bcc.s FormatHex_Word_WriteLastNibble2 50 | rts ; return Carry=1 51 | 52 | ; --------------------------------------------------------------- 53 | FormatHex_LongWord: 54 | swap d1 55 | 56 | FormatHex_LongWord_Swapped: 57 | bsr.s FormatHex_Word 58 | bcs.s FormatHex_Return ; if buffer terminated, branch 59 | 60 | FormatHex_Word_Swapped: 61 | swap d1 62 | ;fallthrough 63 | 64 | ; --------------------------------------------------------------- 65 | FormatHex_Word: 66 | 67 | __it: = 0 68 | rept 4-1 69 | __it: = __it+1 70 | rol.w #4, d1 71 | moveq #$F, d4 72 | and.w d1, d4 ; get nibble 73 | move.b HexDigitToChar(pc,d4), (a0)+ 74 | dbf d7, FormatHex_Word_Nibble\#__it ; if buffer is not exhausted, branch 75 | jsr (a4) ; otherwise, call buffer flush function 76 | bcs.s FormatHex_Return ; if buffer is terminated, branch 77 | FormatHex_Word_Nibble\#__it: ; set label for the next nibble 78 | endr 79 | 80 | FormatHex_Word_WriteLastNibble: 81 | rol.w #4, d1 82 | moveq #$F, d3 83 | 84 | FormatHex_Word_WriteLastNibble2: 85 | and.w d3, d1 ; get nibble 86 | move.b HexDigitToChar(pc,d1), (a0)+ 87 | dbf d7, FormatHex_Return 88 | jmp (a4) ; call buffer flush function and return buffer status 89 | 90 | FormatHex_Return: 91 | rts ; return buffer status 92 | 93 | ; --------------------------------------------------------------- 94 | HexDigitToChar: 95 | dc.b '0123456789ABCDEF' 96 | 97 | ; --------------------------------------------------------------- 98 | FormatHex_LongWord_Trim: 99 | swap d1 100 | beq.s FormatHex_Word_Trim_Swapped ; if high word is 0000, we only have to display the lower one 101 | 102 | FormatHex_LongWord_Trim_Swapped_NonZero: 103 | bsr.s FormatHex_Word_Trim 104 | bcs.s FormatHex_Return ; if buffer terminated, branch 105 | bra FormatHex_Word_Swapped ; should display a word without trimming now 106 | 107 | FormatHex_Word_Trim_Swapped: 108 | swap d1 109 | 110 | ; --------------------------------------------------------------- 111 | FormatHex_Word_Trim: 112 | 113 | __it: = 0 114 | rept 4-2 115 | __it: = __it+1 116 | 117 | rol.w #4, d1 118 | moveq #$F, d4 119 | and.w d1, d4 120 | beq.s FormatHex_Word_Trim_Nibble\#__it ; if nibble is 0, check next nibble (don't draw) 121 | 122 | ; If we get to this point, we are going to branch to "non-trim" routines from now on ... 123 | move.b HexDigitToChar(pc,d4), (a0)+ ; output digit 124 | dbf d7, FormatHex_Word_Nibble\#__it ; if buffer is not exhausted, branch to normal nibble drawing routine 125 | jsr (a4) ; otherwise, call buffer flush function 126 | bcc.s FormatHex_Word_Nibble\#__it ; if buffer is not terminated, branch to normal nibble drawing routine 127 | rts 128 | 129 | FormatHex_Word_Trim_Nibble\#__it: 130 | endr 131 | 132 | ; Pre-last iteration is special as it connects with `FormatHex_Word_WriteLastNibble` 133 | rol.w #4, d1 134 | moveq #$F, d4 135 | and.w d1, d4 136 | beq.s FormatHex_Word_WriteLastNibble ; even if this nibble is 0, the last one is rendered anyways 137 | move.b HexDigitToChar(pc,d4), (a0)+ 138 | dbf d7, FormatHex_Word_WriteLastNibble ; if buffer is not exhausted, branch 139 | jsr (a4) ; otherwise, call buffer flush function 140 | bcc.s FormatHex_Word_WriteLastNibble ; if buffer is not terminated, branch 141 | rts 142 | -------------------------------------------------------------------------------- /modules/core/Formatter_Sym.asm: -------------------------------------------------------------------------------- 1 | 2 | ; =============================================================== 3 | ; --------------------------------------------------------------- 4 | ; Error handling and debugging modules 5 | ; 6 | ; (c) 2016-2023, Vladikcomper 7 | ; --------------------------------------------------------------- 8 | ; String formatters : Symbols 9 | ; --------------------------------------------------------------- 10 | ; INPUT: 11 | ; d1 Value 12 | ; 13 | ; d7 .w Number of bytes left in buffer, minus one 14 | ; a0 String buffer 15 | ; a4 Buffer flush function 16 | ; 17 | ; OUTPUT: 18 | ; (a0)++ ASCII characters for the converted value 19 | ; 20 | ; WARNING! 21 | ; 1) Formatters can only use registers a3 / d0-d4 22 | ; 2) Formatters should decrement d7 after each symbol write. 23 | ; 3) When d7 decrements below 0, a buffer flush function 24 | ; loaded in a4 should be called. The standard function 25 | ; usually renders buffer's contents on the screen (see 26 | ; "Console_FlushBuffer"), then resets the buffer. 27 | ; This function will reload d7, a0 and Carry flag. 28 | ; 4) If Carry flag is set after calling buffer flush function, 29 | ; formatter should halt all further processing and return, 30 | ; retaining the returned Carry bit. 31 | ; --------------------------------------------------------------- 32 | 33 | FormatSym_Handlers: 34 | ext.l d1 ; $00 ; handler for word 35 | bra.s FormatSym ; $02 36 | ; --------------------------------------------------------------- 37 | jmp FormatSym(pc) ; $04 ; handler for longword 38 | ; --------------------------------------------------------------- 39 | ext.w d1 ; $08 ; handler for byte 40 | ext.l d1 41 | 42 | FormatSym: 43 | movem.l d1/d3/a1-a2, -(sp) 44 | jsr GetSymbolByOffset(pc) ; IN: d1 = offset 45 | bne.s FormatSym_ChkUnknownSymbol ; OUT: d0/Z = error status, d1 = displacement, a1 = symbol pointer 46 | move.l d1, (sp) ; replace offset stored in stack as D1 with displacement 47 | jsr DecodeSymbol(pc) ; IN: a1 = symbol pointer 48 | movem.l (sp)+, d1/d3/a1-a2 ; NOTICE: This doesn't affect CCR, so this routine still returns Carry 49 | bcs.s FormatSym_Return ; if got carry (buffer termination), return immediately 50 | 51 | FormatSym_ChkDrawDisplacement: 52 | btst #3, d3 ; is "display just label part so far" bit set? 53 | bne.s FormatSym_Return ; if yes, branch (Z=0 or C=1) 54 | jmp FormatString_CodeHandlers+$40(pc); otherwise, also display displacement now 55 | 56 | FormatSym_Return: 57 | rts 58 | 59 | ; --------------------------------------------------------------- 60 | FormatSym_ChkUnknownSymbol: 61 | movem.l (sp)+, d1/d3/a1-a2 62 | btst #2, d3 ; is "draw on error" bit set? 63 | beq.s FormatSym_ReturnNC ; if not, branch 64 | lea FormatSym_Str_Unknown(pc), a3 65 | jmp FormatString_CodeHandlers+$52(pc) ; jump to code D0 (string) handler, but skip instruction that sets A3 66 | 67 | ; --------------------------------------------------------------- 68 | FormatSym_ReturnNC: 69 | moveq #-1, d0 ; reset Carry, keep D0 an error code 70 | bra.s FormatSym_ChkDrawDisplacement 71 | 72 | ; --------------------------------------------------------------- 73 | FormatSym_Str_Unknown: 74 | dc.b '',0 75 | even 76 | 77 | ; --------------------------------------------------------------- 78 | ; INPUT: 79 | ; d1 .l Displacement 80 | ; --------------------------------------------------------------- 81 | 82 | FormatSym_Displacement: 83 | move.b #'+', (a0)+ 84 | dbf d7, @buffer_ok 85 | jsr (a4) 86 | bcs.s FormatSym_Return 87 | @buffer_ok: 88 | 89 | swap d1 ; swap displacement longword 90 | tst.w d1 ; test higher 16-bits of displacement 91 | beq FormatHex_Word_Trim_Swapped ; if bits are empty, display displacement as word 92 | bra FormatHex_LongWord_Trim_Swapped_NonZero ; otherwise, display longword 93 | 94 | ; --------------------------------------------------------------- 95 | ; INPUT: 96 | ; d1 .l Offset 97 | ; d3 .b Control byte 98 | ; --------------------------------------------------------------- 99 | 100 | FormatSym_Offset: 101 | btst #3, d3 ; is "don't draw offset" flag set? 102 | bne.s FormatSym_Return ; WARNING: Should return NC 103 | jmp FormatHex_LongWord(pc) 104 | 105 | -------------------------------------------------------------------------------- /modules/core/KDebug.asm: -------------------------------------------------------------------------------- 1 | 2 | ; ============================================================================= 3 | ; ----------------------------------------------------------------------------- 4 | ; MD Debugger and Error Handler 5 | ; 6 | ; (c) 2016-2023, Vladikcomper 7 | ; ----------------------------------------------------------------------------- 8 | ; KDebug intergration module 9 | ; ----------------------------------------------------------------------------- 10 | 11 | ; ============================================================================= 12 | ; ----------------------------------------------------------------------------- 13 | ; Write formatted strings to KDebug message buffer 14 | ; ----------------------------------------------------------------------------- 15 | ; INPUT: 16 | ; a1 Pointer to source formatted string 17 | ; a2 Arguments buffer pointer 18 | ; 19 | ; USES: 20 | ; a0-a2, d7 21 | ; ----------------------------------------------------------------------------- 22 | 23 | KDebug_WriteLine_Formatted: __global 24 | pea KDebug_FlushLine(pc) 25 | 26 | ; ----------------------------------------------------------------------------- 27 | KDebug_Write_Formatted: __global 28 | 29 | @buffer_size = __KDEBUG_TEXT_BUFFER_SIZE__ 30 | 31 | move.l a4, -(sp) 32 | lea @FlushBuffer(pc), a4 ; flushing function 33 | lea -@buffer_size(sp), sp ; allocate string buffer 34 | lea (sp), a0 ; a0 = string buffer 35 | moveq #@buffer_size-2, d7 ; d7 = number of characters before flush -1 36 | jsr FormatString(pc) 37 | lea @buffer_size(sp), sp ; free string buffer 38 | move.l (sp)+, a4 39 | rts 40 | 41 | ; --------------------------------------------------------------- 42 | ; Flush buffer callback raised by FormatString 43 | ; --------------------------------------------------------------- 44 | ; INPUT: 45 | ; a0 Buffer position 46 | ; d7 .w Number of characters remaining in buffer - 1 47 | ; 48 | ; OUTPUT: 49 | ; a0 Buffer position after flushing 50 | ; d7 .w Number of characters before next flush - 1 51 | ; Carry 0 = continue operation 52 | ; 1 = terminate FormatString with error condition 53 | ; 54 | ; WARNING: This function can only modify a0 / d7 ! 55 | ; --------------------------------------------------------------- 56 | 57 | @FlushBuffer: 58 | clr.b (a0)+ ; finalize buffer 59 | 60 | neg.w d7 61 | add.w #@buffer_size-1, d7 62 | sub.w d7, a0 ; a0 = start of the buffer 63 | 64 | move.l a0, -(sp) 65 | move.l a5, -(sp) 66 | 67 | lea VDP_Ctrl, a5 68 | move.w #$9E00, d7 69 | bra.s @write_buffer_next 70 | 71 | @write_buffer: 72 | move.w d7, (a5) 73 | 74 | @write_buffer_next: 75 | move.b (a0)+, d7 76 | bgt.s @write_buffer ; if not null-terminator or flag, branch 77 | beq.s @write_buffer_done ; if null-terminator, branch 78 | sub.b #_newl, d7 ; is flag "new line"? 79 | beq.s @write_buffer ; if yes, branch 80 | cmp.b #_setw-_newl, d7 ; is is it flag without arguments? 81 | blt.s @write_buffer_next ; if yes, only skip the flag itself 82 | addq.w #1, a0 ; otherwise, skip argument as well 83 | bra.s @write_buffer_next ; '' 84 | 85 | ; ----------------------------------------------------------------------------- 86 | @write_buffer_done: 87 | 88 | move.l (sp)+, a5 89 | move.l (sp)+, a0 90 | moveq #@buffer_size-2, d7 ; d7 = number of characters before flush -1 91 | rts ; WARNING! Must return Carry=0 92 | 93 | 94 | ; ============================================================================= 95 | ; ----------------------------------------------------------------------------- 96 | ; Finishes the current line and flushes KDebug message buffer 97 | ; ----------------------------------------------------------------------------- 98 | 99 | KDebug_FlushLine: __global 100 | move.w #$9E00, VDP_Ctrl ; send null-terminator 101 | rts 102 | 103 | 104 | ; ============================================================================= 105 | ; ----------------------------------------------------------------------------- 106 | ; Write raw string to KDebug message buffer 107 | ; ----------------------------------------------------------------------------- 108 | ; INPUT: 109 | ; a0 Pointer to null-terminated string 110 | ; 111 | ; OUTPUT: 112 | ; a0 Pointer to the end of string 113 | ; 114 | ; MODIFIES: 115 | ; a0 116 | ; ----------------------------------------------------------------------------- 117 | 118 | KDebug_WriteLine: __global 119 | pea KDebug_FlushLine(pc) 120 | 121 | ; --------------------------------------------------------------- 122 | KDebug_Write: __global 123 | move.w d7, -(sp) 124 | move.l a5, -(sp) 125 | 126 | lea VDP_Ctrl, a5 127 | move.w #$9E00, d7 128 | bra.s @write_buffer_next 129 | 130 | @write_buffer: 131 | move.w d7, (a5) 132 | 133 | @write_buffer_next: 134 | move.b (a0)+, d7 135 | bgt.s @write_buffer ; if not null-terminator or flag, branch 136 | beq.s @write_buffer_done ; if null-terminator, branch 137 | sub.b #_newl, d7 ; is flag "new line"? 138 | beq.s @write_buffer ; if yes, branch 139 | cmp.b #_setw-_newl, d7 ; is is it flag without arguments? 140 | blt.s @write_buffer_next ; if yes, only skip the flag itself 141 | addq.w #1, a0 ; otherwise, skip argument as well 142 | bra.s @write_buffer_next ; '' 143 | 144 | ; ----------------------------------------------------------------------------- 145 | @write_buffer_done: 146 | move.l (sp)+, a5 147 | move.w (sp)+, d7 148 | rts 149 | -------------------------------------------------------------------------------- /modules/core/KDebug.defs.asm: -------------------------------------------------------------------------------- 1 | if def(_KDEBUG_DEFS)=0 2 | _KDEBUG_DEFS: equ 1 3 | 4 | ; =============================================================== 5 | ; --------------------------------------------------------------- 6 | ; Error handling and debugging modules 7 | ; 8 | ; (c) 2016-2023, Vladikcomper 9 | ; --------------------------------------------------------------- 10 | ; KDebug intergration module (definitions only) 11 | ; 12 | ; NOTICE: This defines ASM68K-only macros and partially 13 | ; duplicates shared macro definitions from higher-level bundles. 14 | ; However, this allows to use `KDebug` macros inside the 15 | ; debugger itself. 16 | ; --------------------------------------------------------------- 17 | 18 | ; Default size of a text buffer used by `FormatString`, allocated 19 | ; on the stack. 20 | ; MD Debugger uses a smaller buffer, because the stack is usually 21 | ; quite busy by the time exception is thrown. 22 | if def(__KDEBUG_TEXT_BUFFER_SIZE__)=0 23 | __KDEBUG_TEXT_BUFFER_SIZE__: equ $30 24 | endif 25 | 26 | 27 | KDebug & 28 | macro 29 | 30 | if def(__DEBUG__) ; KDebug interface is only available in DEBUG builds 31 | if strcmp("\0","write")|strcmp("\0","writeline")|strcmp("\0","Write")|strcmp("\0","WriteLine") 32 | move.w sr, -(sp) 33 | 34 | __FSTRING_GenerateArgumentsCode \1 35 | 36 | ; If we have any arguments in string, use formatted string function ... 37 | if (__sp>0) 38 | movem.l a0-a2/d7, -(sp) 39 | lea 4*4(sp), a2 40 | lea @str\@(pc), a1 41 | jsr KDebug_\0\_Formatted(pc) 42 | movem.l (sp)+, a0-a2/d7 43 | if (__sp>8) 44 | lea __sp(sp), sp 45 | elseif (__sp>0) 46 | addq.w #__sp, sp 47 | endif 48 | 49 | ; ... Otherwise, use direct write as an optimization 50 | else 51 | move.l a0, -(sp) 52 | lea @str\@(pc), a0 53 | jsr KDebug_\0(pc) 54 | move.l (sp)+, a0 55 | endif 56 | 57 | move.w (sp)+, sr 58 | bra.w @instr_end\@ 59 | @str\@: 60 | __FSTRING_GenerateDecodedString \1 61 | even 62 | @instr_end\@: 63 | 64 | elseif strcmp("\0","breakline")|strcmp("\0","BreakLine") 65 | move.w sr, -(sp) 66 | jsr KDebug_FlushLine(pc) 67 | move.w (sp)+, sr 68 | 69 | elseif strcmp("\0","starttimer")|strcmp("\0","StartTimer") 70 | move.w sr, -(sp) 71 | move.w #$9FC0, VDP_Ctrl 72 | move.w (sp)+, sr 73 | 74 | elseif strcmp("\0","endtimer")|strcmp("\0","EndTimer") 75 | move.w sr, -(sp) 76 | move.w #$9F00, VDP_Ctrl 77 | move.w (sp)+, sr 78 | 79 | elseif strcmp("\0","breakpoint")|strcmp("\0","BreakPoint") 80 | move.w sr, -(sp) 81 | move.w #$9D00, VDP_Ctrl 82 | move.w (sp)+, sr 83 | 84 | else 85 | inform 2,"""\0"" isn't a member of ""KDebug""" 86 | 87 | endif 88 | endif 89 | endm 90 | 91 | endif -------------------------------------------------------------------------------- /modules/core/Macros.asm: -------------------------------------------------------------------------------- 1 | if def(_MACRO_DEFS)=0 2 | _MACRO_DEFS: equ 1 3 | 4 | ; =============================================================== 5 | ; --------------------------------------------------------------- 6 | ; Error handling and debugging modules 7 | ; 8 | ; (c) 2016-2023, Vladikcomper 9 | ; --------------------------------------------------------------- 10 | ; Macros definitions file 11 | ; --------------------------------------------------------------- 12 | 13 | VDP_Data equ $C00000 14 | VDP_Ctrl equ $C00004 15 | 16 | ; Generate VRAM write command 17 | vram macro offset,operand 18 | if (narg=1) 19 | move.l #($40000000+(((\offset)&$3FFF)<<16)+(((\offset)&$C000)>>14)),VDP_Ctrl 20 | else 21 | move.l #($40000000+(((\offset)&$3FFF)<<16)+(((\offset)&$C000)>>14)),\operand 22 | endif 23 | endm 24 | 25 | ; Generate dc.l constant with VRAM write command 26 | dcvram macro offset 27 | dc.l ($40000000+(((\offset)&$3FFF)<<16)+(((\offset)&$C000)>>14)) 28 | endm 29 | 30 | 31 | ; Generate CRAM write command 32 | cram macro offset,operand 33 | if (narg=1) 34 | move.l #($C0000000+((\offset)<<16)),VDP_Ctrl 35 | else 36 | move.l #($C0000000+((\offset)<<16)),\operand 37 | endif 38 | endm 39 | 40 | ; A special macro to define externally visible symbols 41 | __global macro * 42 | ; __NOGLOBALS__ compile option suppresses global symbols, mostly used by tests 43 | if def(__NOGLOBALS__)=0 44 | ; For linkable builds, use `xdef` to export symbol 45 | if def(__LINKABLE__) 46 | xdef MDDBG__\* 47 | endif ; __LINKABLE__ 48 | 49 | MDDBG__\*: ; full global symbol name uses "MD Debugger" prefix to minimize naming conflicts 50 | endif ; __NOGLOBALS__=0 51 | 52 | \*: ; place the original symbol itself 53 | endm 54 | 55 | endif ; _MACRO_DEFS 56 | -------------------------------------------------------------------------------- /modules/errorhandler-core/Debugger_AddressRegisters.asm: -------------------------------------------------------------------------------- 1 | 2 | ; ============================================================================= 3 | ; ----------------------------------------------------------------------------- 4 | ; MD Debugger and Error Handler 5 | ; 6 | ; (c) 2016-2023, Vladikcomper 7 | ; ----------------------------------------------------------------------------- 8 | ; Address registers debugger 9 | ; ----------------------------------------------------------------------------- 10 | 11 | 12 | include "..\core\Macros.asm" ; for "__global" 13 | include "..\core\Console.defs.asm" ; for "_pal0", "_newl" etc 14 | 15 | 16 | Debugger_AddressRegisters: __global 17 | movem.l a0-a6, -(sp) ; dump registers 18 | 19 | ; Setup screen header (position and "Address Registers:" text) 20 | lea @Str_ScreenHeader(pc), a0 21 | jsr Console_Write(pc) 22 | 23 | ; Render registers table 24 | lea (sp), a4 ; get registers dump in the stack ... 25 | moveq #7-1, d6 ; number of registers to process (minus one) 26 | 27 | move.w #' '<<8, -(sp) ; STACK => dc.b _pal0, 'a0: ', 0 28 | move.l #(_pal0<<24)|'a0:', -(sp) ; '' 29 | 30 | @loop: 31 | lea (sp), a0 ; a0 = label 32 | move.l (a4)+, d1 ; d1 = address register value 33 | jsr Error_DrawOffsetLocation(pc) 34 | 35 | addq.b #1, 2(sp) ; add 1 to register's digit ASCII 36 | dbf d6, @loop 37 | 38 | lea 6+4*7(sp), sp ; STACK => free string buffer and registers themselves 39 | rts 40 | 41 | ; ----------------------------------------------------------------------------- 42 | @Str_ScreenHeader: 43 | dc.b _newl, _setx, 1, _setw, 38 44 | dc.b _pal1, 'Address Registers:', _newl, _newl, 0 45 | even 46 | -------------------------------------------------------------------------------- /modules/errorhandler-core/Debugger_Backtrace.asm: -------------------------------------------------------------------------------- 1 | 2 | ; ============================================================================= 3 | ; ----------------------------------------------------------------------------- 4 | ; MD Debugger and Error Handler 5 | ; 6 | ; (c) 2016-2023, Vladikcomper 7 | ; ----------------------------------------------------------------------------- 8 | ; Backtrace debugger 9 | ; ----------------------------------------------------------------------------- 10 | 11 | 12 | include "..\core\Macros.asm" ; for "__global" 13 | include "..\core\Console.defs.asm" ; for "_pal0", "_newl" etc 14 | 15 | 16 | Debugger_Backtrace: __global 17 | ; Setup screen header (position and "Backtrace:" text) 18 | lea @Str_ScreenHeader(pc), a0 19 | jsr Console_Write(pc) 20 | 21 | ; ---------------------------------------- 22 | ; Build backtrace routine 23 | ; ---------------------------------------- 24 | 25 | ; Registers layout: 26 | @data0: equr d0 27 | @data1: equr d1 28 | @addr0: equr a0 29 | @stack_top: equr a1 30 | @stack_curr: equr a2 31 | 32 | move.l 0.w, @stack_top 33 | subq.l #4, @stack_top ; set final longword to read 34 | lea (sp), @stack_curr 35 | jsr Error_MaskStackBoundaries(pc) 36 | 37 | cmpa.l @stack_curr, @stack_top ; are we within stack? 38 | blo.s @done ; if not, branch 39 | 40 | @try_offset_loop: 41 | cmpi.w #$40, (@stack_curr) ; is address within ROM ($000000..$3FFFFF)? 42 | bhs.s @try_next_offset ; if not, branch 43 | move.l (@stack_curr), @data0 ; @data0 = possible return address 44 | beq.s @try_next_offset ; if address is zero, branch 45 | movea.l @data0, @addr0 ; @addr0 = possible return address 46 | andi.w #1, @data0 ; is address even? 47 | bne.s @try_next_offset ; if not, branch 48 | 49 | ; Trying to find JSR/BSR instructions before the return address 50 | @chk_2byte: 51 | ; 2-byte instructions: 52 | move.b -(@addr0), @data1 53 | move.b -(@addr0), @data0 54 | 55 | @chk_2byte_bsr: 56 | ; BSR.s = %01000001 XXXXXXXX 57 | cmp.b #$61, @data0 ; is instruction BSR.s? 58 | bne.s @chk_2byte_jsr ; if not, branch 59 | tst.b @data1 ; BSR.s must use non-zero displacement 60 | bne.s @offset_is_caller ; if yes, branch 61 | 62 | @chk_2byte_jsr: 63 | ; JSR (an) = %01001110 10010XXX 64 | cmp.b #$4E, @data0 ; is instruction JSR? 65 | bne.s @chk_4byte ; if not, branch 66 | and.b #%11111000, @data1 ; clear out "EARegister" part 67 | cmp.b #%10010000, @data1 ; is mode (an)? 68 | beq.s @offset_is_caller ; if yes, branch 69 | 70 | @chk_4byte: 71 | move.w -(@addr0), @data0 72 | 73 | @chk_4byte_bsr: 74 | ; BSR.w = %01000001 00000000 XXXXXXXX XXXXXXXX 75 | cmp.w #$6100, @data0 ; is instruction BSR.w? 76 | beq.s @offset_is_caller ; if yes, branch 77 | 78 | @chk_4byte_jsr: 79 | ; JSR d16(an) = %01001110 10101XXX XXXXXXXX XXXXXXXX 80 | ; JSR d8(an,xn) = %01001110 10110XXX XXXXXXXX XXXXXXXX 81 | ; JSR (xxx).w = %01001110 10111000 XXXXXXXX XXXXXXXX 82 | ; JSR d16(pc) = %01001110 10111010 XXXXXXXX XXXXXXXX 83 | ; JSR d8(pc,xn) = %01001110 10111011 XXXXXXXX XXXXXXXX 84 | move.b @data0, @data1 85 | clr.b @data0 86 | cmpi.w #$4E00, @data0 ; is instruction JSR? 87 | bne.s @chk_6byte ; if not, branch 88 | cmp.b #%10101000, @data1 ; low byte should be between %10101000 89 | blo.s @chk_6byte 90 | cmp.b #%10111011, @data1 ; ... and %10111011 91 | bhi.s @chk_6byte 92 | cmp.b #%10111001, @data1 ; JSR (xxx).l is invalid, because it's not 4 bytes! 93 | bne.s @offset_is_caller 94 | 95 | @chk_6byte: 96 | @chk_6byte_jsr: 97 | ; JSR (xxx).l = %01001110 10111001 XXXXXXXX XXXXXXXX 98 | cmp.w #%0100111010111001, -(@addr0) ; is instruction JSR (xxx).l? 99 | bne.s @try_next_offset ; if not, branch 100 | 101 | @offset_is_caller: 102 | move.l @stack_curr, -(sp) 103 | move.l @stack_top, -(sp) 104 | move.l @addr0, d1 ; d1 = offset 105 | jsr Error_DrawOffsetLocation2(pc) 106 | move.l (sp)+, @stack_top 107 | move.l (sp)+, @stack_curr 108 | 109 | addq.l #2, @stack_curr ; for +4 (see below) 110 | 111 | @try_next_offset: 112 | addq.l #2, @stack_curr 113 | cmpa.l @stack_curr, @stack_top 114 | bhs @try_offset_loop 115 | 116 | @done: 117 | rts 118 | 119 | ; ----------------------------------------------------------------------------- 120 | @Str_ScreenHeader: 121 | dc.b _newl, _setx, 1, _setw, 38 122 | dc.b _pal1, 'Backtrace:', _newl, _newl, 0 123 | even 124 | -------------------------------------------------------------------------------- /modules/errorhandler-core/Exceptions.asm: -------------------------------------------------------------------------------- 1 | 2 | ; ============================================================================= 3 | ; ----------------------------------------------------------------------------- 4 | ; MD Debugger and Error Handler 5 | ; 6 | ; (c) 2016-2024, Vladikcomper 7 | ; ----------------------------------------------------------------------------- 8 | ; Standard Exception Vectors 9 | ; ----------------------------------------------------------------------------- 10 | 11 | ; ----------------------------------------------------------------------------- 12 | ; NOTICE: This code is only included in MD Shell and "asm68k-linkable" bundle 13 | ; of MD Debugger. Other bundles allow customization outside of this blob. 14 | ; ----------------------------------------------------------------------------- 15 | 16 | 17 | ; Screen appearence flags 18 | _eh_address_error equ $01 ; use for address and bus errors only (tells error handler to display additional "Address" field) 19 | _eh_show_sr_usp equ $02 ; displays SR and USP registers content on error screen 20 | _eh_hide_caller equ $04 ; don't guess and print caller in the header (in SGDK and C/C++ projects naive caller detection isn't reliable) 21 | 22 | ; Advanced execution flags 23 | ; WARNING! For experts only, DO NOT USE them unless you know what you're doing 24 | _eh_return equ $20 25 | _eh_enter_console equ $40 26 | _eh_align_offset equ $80 27 | 28 | ; Default error handler configuration 29 | if def(__EH_DEFAULT_CONFIG__)=0 30 | _eh_default equ 0 ;_eh_show_sr_usp 31 | else 32 | _eh_default equ __EH_DEFAULT_CONFIG__ 33 | endif 34 | 35 | ; ----------------------------------------------------------------------------- 36 | __ErrorMessage: macro string, opts 37 | jsr ErrorHandler(pc) 38 | dc.b \string, 0 39 | dc.b \opts+_eh_return|(((*&1)^1)*_eh_align_offset) ; add flag "_eh_align_offset" if the next byte is at odd offset ... 40 | even ; ... to tell Error handler to skip this byte, so it'll jump to ... 41 | jmp ErrorHandler_PagesController(pc) ; ... extensions controller 42 | endm 43 | 44 | ; ----------------------------------------------------------------------------- 45 | 46 | BusError: __global 47 | __ErrorMessage "BUS ERROR", _eh_default|_eh_address_error 48 | 49 | AddressError: __global 50 | __ErrorMessage "ADDRESS ERROR", _eh_default|_eh_address_error 51 | 52 | IllegalInstr: __global 53 | __ErrorMessage "ILLEGAL INSTRUCTION", _eh_default 54 | 55 | ZeroDivide: __global 56 | __ErrorMessage "ZERO DIVIDE", _eh_default 57 | 58 | ChkInstr: __global 59 | __ErrorMessage "CHK INSTRUCTION", _eh_default 60 | 61 | TrapvInstr: __global 62 | __ErrorMessage "TRAPV INSTRUCTION", _eh_default 63 | 64 | PrivilegeViol: __global 65 | __ErrorMessage "PRIVILEGE VIOLATION", _eh_default 66 | 67 | Trace: __global 68 | __ErrorMessage "TRACE", _eh_default 69 | 70 | Line1010Emu: __global 71 | __ErrorMessage "LINE 1010 EMULATOR", _eh_default 72 | 73 | Line1111Emu: __global 74 | __ErrorMessage "LINE 1111 EMULATOR", _eh_default 75 | 76 | ErrorExcept: __global 77 | __ErrorMessage "ERROR EXCEPTION", _eh_default 78 | -------------------------------------------------------------------------------- /modules/errorhandler-core/Font.asm: -------------------------------------------------------------------------------- 1 | 2 | ; ============================================================================= 3 | ; ----------------------------------------------------------------------------- 4 | ; MD Debugger and Error Handler 5 | ; 6 | ; (c) 2016-2023, Vladikcomper 7 | ; ----------------------------------------------------------------------------- 8 | ; Error Handler 1bpp font graphics 9 | ; ----------------------------------------------------------------------------- 10 | 11 | Art1bpp_Font: __global 12 | dc.w Art1bpp_Font_End-Art1bpp_Font_Start-1 ; font size - 1 13 | 14 | Art1bpp_Font_Start: 15 | dc.l $00000000, $00000000, $183C3C18, $18001800, $6C6C6C00, $00000000, $6C6CFE6C, $FE6C6C00 16 | dc.l $187EC07C, $06FC1800, $00C60C18, $3060C600, $386C3876, $CCCC7600, $18183000, $00000000 17 | dc.l $18306060, $60301800, $60301818, $18306000, $00EE7CFE, $7CEE0000, $0018187E, $18180000 18 | dc.l $00000000, $18183000, $000000FE, $00000000, $00000000, $00383800, $060C1830, $60C08000 19 | dc.l $7CC6CEDE, $F6E67C00, $18781818, $18187E00, $7CC60C18, $3066FE00, $7CC6063C, $06C67C00 20 | dc.l $0C1C3C6C, $FE0C0C00, $FEC0FC06, $06C67C00, $7CC6C0FC, $C6C67C00, $FEC6060C, $18181800 21 | dc.l $7CC6C67C, $C6C67C00, $7CC6C67E, $06C67C00, $001C1C00, $001C1C00, $00181800, $00181830 22 | dc.l $0C183060, $30180C00, $0000FE00, $00FE0000, $6030180C, $18306000, $7CC6060C, $18001800 23 | dc.l $7CC6C6DE, $DCC07E00, $386CC6C6, $FEC6C600, $FC66667C, $6666FC00, $3C66C0C0, $C0663C00 24 | dc.l $F86C6666, $666CF800, $FEC2C0F8, $C0C2FE00, $FE62607C, $6060F000, $7CC6C0C0, $DEC67C00 25 | dc.l $C6C6C6FE, $C6C6C600, $3C181818, $18183C00, $3C181818, $D8D87000, $C6CCD8F0, $D8CCC600 26 | dc.l $F0606060, $6062FE00, $C6EEFED6, $D6C6C600, $C6E6E6F6, $DECEC600, $7CC6C6C6, $C6C67C00 27 | dc.l $FC66667C, $6060F000, $7CC6C6C6, $C6D67C06, $FCC6C6FC, $D8CCC600, $7CC6C07C, $06C67C00 28 | dc.l $7E5A1818, $18183C00, $C6C6C6C6, $C6C67C00, $C6C6C6C6, $6C381000, $C6C6D6D6, $FEEEC600 29 | dc.l $C66C3838, $386CC600, $6666663C, $18183C00, $FE860C18, $3062FE00, $7C606060, $60607C00 30 | dc.l $C0603018, $0C060200, $7C0C0C0C, $0C0C7C00, $10386CC6, $00000000, $00000000, $000000FF 31 | dc.l $30301800, $00000000, $0000780C, $7CCC7E00, $E0607C66, $6666FC00, $00007CC6, $C0C67C00 32 | dc.l $1C0C7CCC, $CCCC7E00, $00007CC6, $FEC07C00, $1C3630FC, $30307800, $000076CE, $C67E067C 33 | dc.l $E0607C66, $6666E600, $18003818, $18183C00, $0C001C0C, $0C0CCC78, $E060666C, $786CE600 34 | dc.l $18181818, $18181C00, $00006CFE, $D6D6C600, $0000DC66, $66666600, $00007CC6, $C6C67C00 35 | dc.l $0000DC66, $667C60F0, $000076CC, $CC7C0C1E, $0000DC66, $6060F000, $00007CC0, $7C067C00 36 | dc.l $3030FC30, $30361C00, $0000CCCC, $CCCC7600, $0000C6C6, $6C381000, $0000C6C6, $D6FE6C00 37 | dc.l $0000C66C, $386CC600, $0000C6C6, $CE76067C, $0000FC98, $3064FC00, $0E181870, $18180E00 38 | dc.l $18181800, $18181800, $7018180E, $18187000, $76DC0000, $00000000 39 | 40 | Art1bpp_Font_End: 41 | -------------------------------------------------------------------------------- /modules/errorhandler-core/Main.asm: -------------------------------------------------------------------------------- 1 | 2 | ; ============================================================================= 3 | ; ----------------------------------------------------------------------------- 4 | ; MD Debugger and Error Handler 5 | ; 6 | ; (c) 2016-2024, Vladikcomper 7 | ; ----------------------------------------------------------------------------- 8 | 9 | ; Use smaller text buffers in `Console` and `KDebug` modules, because those are 10 | ; stack-allocated and we can be short on stack space during exceptions. 11 | __CONSOLE_TEXT_BUFFER_SIZE__: equ $10 12 | __KDEBUG_TEXT_BUFFER_SIZE__: equ $10 13 | 14 | ; ----------------------------------------------------------------------------- 15 | ; Main Error Handler module 16 | ; ----------------------------------------------------------------------------- 17 | 18 | ; NOTICE: Here and below "..\errorhandler-core" is used as a workaround 19 | ; to also make this code compile from "md-shell" directory. 20 | include '..\errorhandler-core\ErrorHandler.asm' 21 | 22 | ; ----------------------------------------------------------------------------- 23 | ; Data 24 | ; ----------------------------------------------------------------------------- 25 | 26 | include '..\errorhandler-core\Font.asm' 27 | 28 | ; ----------------------------------------------------------------------------- 29 | ; Core modules 30 | ; ----------------------------------------------------------------------------- 31 | 32 | include '..\core\Symbols.asm' 33 | include '..\core\Formatter_Hex.asm' 34 | include '..\core\Formatter_Bin.asm' 35 | include '..\core\Formatter_Dec.asm' 36 | include '..\core\Formatter_Sym.asm' 37 | include '..\core\Format_String.asm' 38 | include '..\core\Console.asm' 39 | include '..\core\1bpp_Decompress.asm' 40 | 41 | ; ----------------------------------------------------------------------------- 42 | ; Extensions 43 | ; ----------------------------------------------------------------------------- 44 | 45 | include '..\core\KDebug.asm' 46 | include '..\errorhandler-core\Extensions.asm' 47 | include '..\errorhandler-core\Debugger_AddressRegisters.asm' 48 | include '..\errorhandler-core\Debugger_Backtrace.asm' 49 | 50 | if def(__LINKABLE__)|def(__HEADLESS__) 51 | ; ----------------------------------------------------------------------------- 52 | ; Linkable builds include pre-defined exception vectors 53 | ; ----------------------------------------------------------------------------- 54 | 55 | include '..\errorhandler-core\Exceptions.asm' 56 | endif 57 | 58 | if def(__EXTSYM__)=0 59 | ; ----------------------------------------------------------------------------- 60 | ; Symbol table at the end of the ROM 61 | ; ----------------------------------------------------------------------------- 62 | 63 | SymbolData: 64 | endif 65 | -------------------------------------------------------------------------------- /modules/errorhandler-core/inject-tables/ErrorHandler.Blob.txt: -------------------------------------------------------------------------------- 1 | Error_DrawOffsetLocation__inj+2:word:inline: DEBUGGER__STR_OFFSET_SELECTOR-MDDBG__Error_DrawOffsetLocation__inj-2 2 | ErrorHandler_ExtraDebuggerList+0:long:inline: DEBUGGER__EXTENSIONS__BTN_A_DEBUGGER 3 | ErrorHandler_ExtraDebuggerList+4:long:inline: DEBUGGER__EXTENSIONS__BTN_C_DEBUGGER 4 | ErrorHandler_ExtraDebuggerList+8:long:inline: DEBUGGER__EXTENSIONS__BTN_B_DEBUGGER -------------------------------------------------------------------------------- /modules/errorhandler-core/inject-tables/ErrorHandler.ExtSymbols.Blob.txt: -------------------------------------------------------------------------------- 1 | __inject_symboldata_ptr_1+2:long:inline: SymbolData_Ptr 2 | __inject_symboldata_ptr_2+2:long:inline: SymbolData_Ptr 3 | Error_DrawOffsetLocation__inj+2:word:inline: DEBUGGER__STR_OFFSET_SELECTOR-MDDBG__Error_DrawOffsetLocation__inj-2 4 | ErrorHandler_ExtraDebuggerList+0:long:inline: DEBUGGER__EXTENSIONS__BTN_A_DEBUGGER 5 | ErrorHandler_ExtraDebuggerList+4:long:inline: DEBUGGER__EXTENSIONS__BTN_C_DEBUGGER 6 | ErrorHandler_ExtraDebuggerList+8:long:inline: DEBUGGER__EXTENSIONS__BTN_B_DEBUGGER -------------------------------------------------------------------------------- /modules/errorhandler-core/inject-tables/ErrorHandler.GAS.Blob.txt: -------------------------------------------------------------------------------- 1 | __inject_symboldata_ptr_1+2:long:inline: MDDBG__SymbolDataPtr 2 | __inject_symboldata_ptr_2+2:long:inline: MDDBG__SymbolDataPtr 3 | ErrorHandler_ExtraDebuggerList+0:long:inline: MDDBG__Debugger_AddressRegisters 4 | ErrorHandler_ExtraDebuggerList+8:long:inline: MDDBG__Debugger_Backtrace -------------------------------------------------------------------------------- /modules/errorhandler-core/tests/.Makefile: -------------------------------------------------------------------------------- 1 | 2 | # WARNING! It's not recommended to invoke this Makefile manually, since it doesn't track test dependencies. 3 | # Please run `make tests` from the upper directory instead. 4 | 5 | include ../../../utils/.Makefile # For $(CONVSYM), $(CBUNDLE) etc 6 | 7 | ASM68K := wine $(realpath ../../exec/asm68k.exe) 8 | ASFLAGS := /q /k /m /o c+,ws+,op+,os+,ow+,oz+,oaq+,osq+,omq+,ae-,v+ 9 | 10 | TEST_BUILD_DIR := ../../../build/modules/errorhandler-core/tests 11 | 12 | 13 | 14 | .PHONY: all formatstring fullexception guesscaller clean 15 | 16 | all: formatstring fullexception guesscaller 17 | 18 | clean: 19 | rm -f $(TEST_BUILD_DIR)/* 20 | 21 | 22 | formatstring: | $(TEST_BUILD_DIR) $(CONVSYM) 23 | $(ASM68K) $(ASFLAGS) /p FormatString.asm, $(TEST_BUILD_DIR)/FormatString.gen, $(TEST_BUILD_DIR)/FormatString.sym, $(TEST_BUILD_DIR)/FormatString.lst 24 | $(CONVSYM) FormatString_DummySymbols.log $(TEST_BUILD_DIR)/FormatString.gen -a -range 0 FFFFFF -input log -output deb2 25 | $(CONVSYM) $(TEST_BUILD_DIR)/FormatString.sym $(TEST_BUILD_DIR)/FormatString.gen -a -ref 200 26 | 27 | fullexception: | $(TEST_BUILD_DIR) $(CONVSYM) 28 | cd .. && $(ASM68K) $(ASFLAGS) /p tests/FullException.asm, tests/$(TEST_BUILD_DIR)/FullException.gen, tests/$(TEST_BUILD_DIR)/FullException.sym, tests/$(TEST_BUILD_DIR)/FullException.lst 29 | $(CONVSYM) $(TEST_BUILD_DIR)/FullException.sym $(TEST_BUILD_DIR)/FullException.gen -a 30 | 31 | guesscaller: | $(TEST_BUILD_DIR) $(CONVSYM) 32 | cd .. && $(ASM68K) $(ASFLAGS) /p tests/GuessCaller.asm, tests/$(TEST_BUILD_DIR)/GuessCaller.gen, tests/$(TEST_BUILD_DIR)/GuessCaller.sym, tests/$(TEST_BUILD_DIR)/GuessCaller.lst 33 | $(CONVSYM) $(TEST_BUILD_DIR)/GuessCaller.sym $(TEST_BUILD_DIR)/GuessCaller.gen -a -ref 200 34 | 35 | 36 | $(TEST_BUILD_DIR): 37 | mkdir -p $(TEST_BUILD_DIR) 38 | -------------------------------------------------------------------------------- /modules/errorhandler-core/tests/.Makefile.win: -------------------------------------------------------------------------------- 1 | 2 | # WARNING! It's not recommended to invoke this Makefile manually, since it doesn't track test dependencies. 3 | # Please run `make tests` from the upper directory instead. 4 | 5 | include ../../../utils/.Makefile.win # For $(CONVSYM), $(CBUNDLE) etc 6 | 7 | ASM68K := ..\..\exec\asm68k.exe 8 | ASFLAGS := /q /k /m /o c+,ws+,op+,os+,ow+,oz+,oaq+,osq+,omq+,ae-,v+ 9 | 10 | TEST_BUILD_DIR := ..\..\..\build\modules\errorhandler-core\tests 11 | 12 | 13 | 14 | .PHONY: all formatstring fullexception guesscaller clean 15 | 16 | all: formatstring fullexception guesscaller 17 | 18 | clean: 19 | del /q /f $(TEST_BUILD_DIR)\* 20 | 21 | 22 | formatstring: | $(TEST_BUILD_DIR) $(CONVSYM) 23 | $(ASM68K) $(ASFLAGS) /p FormatString.asm, $(TEST_BUILD_DIR)\FormatString.gen, $(TEST_BUILD_DIR)\FormatString.sym, $(TEST_BUILD_DIR)\FormatString.lst 24 | $(CONVSYM) FormatString_DummySymbols.log $(TEST_BUILD_DIR)\FormatString.gen -a -range 0 FFFFFF -input log -output deb2 25 | $(CONVSYM) $(TEST_BUILD_DIR)\FormatString.sym $(TEST_BUILD_DIR)\FormatString.gen -a -ref 200 26 | 27 | fullexception: | $(TEST_BUILD_DIR) $(CONVSYM) 28 | cd .. && tests\$(ASM68K) $(ASFLAGS) /p tests\FullException.asm, tests\$(TEST_BUILD_DIR)\FullException.gen, tests\$(TEST_BUILD_DIR)\FullException.sym, tests\$(TEST_BUILD_DIR)\FullException.lst 29 | $(CONVSYM) $(TEST_BUILD_DIR)\FullException.sym $(TEST_BUILD_DIR)\FullException.gen -a 30 | 31 | guesscaller: | $(TEST_BUILD_DIR) $(CONVSYM) 32 | cd .. && tests\$(ASM68K) $(ASFLAGS) /p tests\GuessCaller.asm, tests\$(TEST_BUILD_DIR)\GuessCaller.gen, tests\$(TEST_BUILD_DIR)\GuessCaller.sym, tests\$(TEST_BUILD_DIR)\GuessCaller.lst 33 | $(CONVSYM) $(TEST_BUILD_DIR)\GuessCaller.sym $(TEST_BUILD_DIR)\GuessCaller.gen -a -ref 200 34 | 35 | 36 | $(TEST_BUILD_DIR): 37 | -md $(TEST_BUILD_DIR) 38 | -------------------------------------------------------------------------------- /modules/errorhandler-core/tests/FormatString_DummySymbols.log: -------------------------------------------------------------------------------- 1 | 100: Offset_100 2 | 200: Offset_200 3 | 280: Offset_280 4 | 281: Offset_280.local 5 | 1000: ShouldOverflowBufferWithDis 6 | 1002: ShouldOverflowBufferWithDisp 7 | 1004: ShouldOverflowBufferWithDisp2 8 | 1006: ShouldOverflowBufferEvenWithoutD 9 | 1008: ShouldOverflowBufferEvenWithoutDisp 10 | 100A: ShouldOverflowBufferEvenWithoutD+ 11 | 10000: short_data_chunk 12 | 10080: long_data_chunk 13 | FF0000: RAM_Offset_FF0000 14 | FFFF8000: RAM_Offset_FFFF8000 15 | FFFFFFFF: RAM_End 16 | -------------------------------------------------------------------------------- /modules/errorhandler-core/tests/GuessCaller.asm: -------------------------------------------------------------------------------- 1 | 2 | ; =============================================================== 3 | ; --------------------------------------------------------------- 4 | ; Error handling and debugging modules 5 | ; 2016-2023, Vladikcomper 6 | ; --------------------------------------------------------------- 7 | ; Guess caller testing module 8 | ; --------------------------------------------------------------- 9 | 10 | include "..\..\build\modules\mdshell\asm68k\MDShell.asm" 11 | 12 | ; --------------------------------------------------------------- 13 | 14 | jsr_test macro loc 15 | Console.Write "\loc\... " 16 | jsr \loc 17 | Console.WriteLine "OK" 18 | endm 19 | 20 | 21 | ; --------------------------------------------------------------- 22 | Main: 23 | jsr_test MaskStackBoundaries_Tests 24 | jsr_test GuessCallerTests 25 | 26 | Console.WriteLine "ALL DONE" 27 | rts 28 | 29 | ; --------------------------------------------------------------- 30 | ; Test "Error_MaskStackBoundaries" 31 | ; --------------------------------------------------------------- 32 | 33 | MaskStackBoundaries_Tests: 34 | 35 | @stack_top: equr a1 36 | @stack_bottom: equr a2 37 | @stack_top_expected: equr a3 38 | @stack_bottom_expected: equr a4 39 | @test_func: equr a5 40 | @test_data: equr a6 41 | 42 | @test_cnt: equr d7 43 | 44 | @test_io: reg @stack_top-@stack_bottom_expected 45 | 46 | lea Error_MaskStackBoundaries, @test_func 47 | lea @TestData, @test_data 48 | moveq #(@TestData_End-@TestData)/16-1, @test_cnt 49 | 50 | @RunTest: 51 | movem.l (@test_data)+, @test_io 52 | jsr (@test_func) 53 | assert.l @stack_top, eq, @stack_top_expected 54 | assert.l @stack_bottom, eq, @stack_bottom_expected 55 | dbf @test_cnt, @RunTest 56 | 57 | rts 58 | 59 | ; --------------------------------------------------------------- 60 | @TestData: 61 | ; Inputs Expected output 62 | dc.l $FF0000, $FF0000, $FF0000, $FF0000 63 | dc.l $FFFF0000, $FFFF0000, $FF0000, $FF0000 64 | dc.l $FFFF8000, $FF0000, $FF8000, $FF0000 65 | dc.l $FF0000, $FFFF8000, $FF0000, $FF8000 66 | dc.l $0, $0, $0, $0 67 | 68 | @TestData_End: 69 | 70 | ; --------------------------------------------------------------- 71 | ; Test "Error_GuessCaller" 72 | ; --------------------------------------------------------------- 73 | 74 | GuessCallerTests: 75 | 76 | @stack_top: equr a1 77 | @stack_bottom: equr a2 78 | @test_func: equr a5 79 | @test_data: equr a6 80 | 81 | @stack_input: reg d0-d3 82 | @offset: equr d1 83 | @offset_expected: equr d2 84 | @test_cnt: equr d7 85 | 86 | lea Error_GuessCaller, @test_func 87 | lea @TestData, @test_data 88 | moveq #(@TestData_End-@TestData)/24-1, @test_cnt 89 | 90 | @RunTest: 91 | move.l (@test_data)+, @stack_top 92 | lea -$10(@stack_top), @stack_bottom 93 | movem.l (@test_data)+, @stack_input 94 | movem.l @stack_input, (@stack_bottom) 95 | 96 | jsr (@test_func) 97 | 98 | move.l (@test_data)+, @offset_expected 99 | assert.l @offset, eq, @offset_expected 100 | 101 | dbf @test_cnt, @RunTest 102 | 103 | rts 104 | 105 | ; --------------------------------------------------------------- 106 | @TestData: 107 | dc.l 0 ; stack top 108 | dc.w $2700, $0000, $003F, $FFFF, $00FE, $FFFE, $0001, $0000 ; contents (16 bytes) 109 | dc.l $10000 ; expected output 110 | 111 | dc.l 0 ; stack top 112 | dc.w $2700, $0000, $0000, $0000, $0001, $0002, $0001, $0000 ; contents (16 bytes) 113 | dc.l $10002 ; expected output 114 | 115 | dc.l 0 ; stack top 116 | dc.w $FFFF, $FFFF, $FFFF, $FFFF, $FFFF, $FFFF, $FFFF, $FFFF ; contents (16 bytes) 117 | dc.l $0 ; expected output 118 | 119 | dc.l -$300 ; stack top 120 | dc.w $2700, $0000, $003F, $FFFF, $00FE, $FFFE, $0001, $0000 ; contents (16 bytes) 121 | dc.l $10000 ; expected output 122 | 123 | dc.l -$300 ; stack top 124 | dc.w $2700, $0000, $0000, $0000, $0001, $0002, $0001, $0000 ; contents (16 bytes) 125 | dc.l $10002 ; expected output 126 | 127 | dc.l $FF8002 ; stack top 128 | dc.w $2700, $0000, $003F, $FFFF, $00FE, $FFFE, $0001, $0000 ; contents (16 bytes) 129 | dc.l $10000 ; expected output 130 | 131 | dc.l $FF8002 ; stack top 132 | dc.w $2700, $0000, $0000, $0000, $0001, $0002, $0001, $0000 ; contents (16 bytes) 133 | dc.l $10002 ; expected output 134 | @TestData_End: 135 | 136 | ; --------------------------------------------------------------- 137 | 138 | ; Disable "__global" macro to avoid naming conflicts with MDShell 139 | __NOGLOBALS__: equ 1 140 | 141 | include "Main.asm" 142 | -------------------------------------------------------------------------------- /modules/errorhandler/BUILD.md: -------------------------------------------------------------------------------- 1 | 2 | # Building Error Handler 3 | 4 | Make sure you're in the right directory: 5 | 6 | ```sh 7 | cd modules/errorhandler/ 8 | ``` 9 | 10 | Use the appropriate GNU Make invocation depending on your system: 11 | 12 | - **Linux/MacOS**: `make` 13 | - **FreeBSD**: `gmake` 14 | - **Windows**: `make -f Makefile.win` 15 | 16 | Bundles will be generated in `build/modules/errorhandler`. 17 | 18 | ## Testing 19 | 20 | Various tests are also provided in form of compilable Mega-Drive ROMs to make sure MD-Shell is stable and usable. 21 | 22 | To build tests, use the appropriate GNU Make invocation depending on your system: 23 | 24 | - **Linux/MacOS**: `make tests` 25 | - **FreeBSD**: `gmake tests` 26 | - **Windows**: `make -f Makefile.win tests` 27 | 28 | ROMs will be generated in `build/modules/errorhandler/tests`. Note that **this won't actually test anything yet**! You need to manually run ROMs in your favorite Sega Mega-Drive emulator to see the results. 29 | -------------------------------------------------------------------------------- /modules/errorhandler/Debugger.Config.asm: -------------------------------------------------------------------------------- 1 | ; --------------------------------------------------------------- 2 | ; Debugger customization 3 | ; --------------------------------------------------------------- 4 | #ifdef MD-SHELL 5 | #ifndef LINKABLE 6 | 7 | ; VBlank interrupt handler (default is "IdleInt", which returns immediately) 8 | MDSHELL__VBLANK_HANDLER: equ MDDBG__IdleInt 9 | 10 | ; HBlank interrupt handler (default is "IdleInt", which returns immediately) 11 | MDSHELL__HBLANK_HANDLER: equ MDDBG__IdleInt 12 | #endif 13 | #endif 14 | 15 | ; Enable debugger extensions 16 | ; Pressing A/B/C on the exception screen can open other debuggers 17 | ; Pressing Start or unmapped button returns to the exception 18 | DEBUGGER__EXTENSIONS__ENABLE: equ 1 ; 0 = OFF, 1 = ON (default) 19 | 20 | #ifndef LINKABLE 21 | ; Whether to show SR and USP registers in exception handler 22 | DEBUGGER__SHOW_SR_USP: equ 0 ; 0 = OFF (default), 1 = ON 23 | 24 | ; Debuggers mapped to pressing A/B/C on the exception screen 25 | ; Use 0 to disable button, use debugger's entry point otherwise. 26 | DEBUGGER__EXTENSIONS__BTN_A_DEBUGGER: equ MDDBG__Debugger_AddressRegisters ; display address register symbols 27 | DEBUGGER__EXTENSIONS__BTN_B_DEBUGGER: equ MDDBG__Debugger_Backtrace ; display exception backtrace 28 | DEBUGGER__EXTENSIONS__BTN_C_DEBUGGER: equ 0 ; disabled 29 | 30 | ; Selects between 24-bit (compact) and 32-bit (full) offset format. 31 | ; This affects offset format next to the symbols in the exception screen header. 32 | ; M68K bus is limited to 24 bits anyways, so not displaying unused bits saves screen space. 33 | ; Possible values: 34 | ; - MDDBG__Str_OffsetLocation_24bit (example: 001C04 SomeLoc+4) 35 | ; - MDDBG__Str_OffsetLocation_32bit (example: 00001C04 SomeLoc+4) 36 | DEBUGGER__STR_OFFSET_SELECTOR: equ MDDBG__Str_OffsetLocation_24bit 37 | 38 | #endif -------------------------------------------------------------------------------- /modules/errorhandler/Debugger.Constants.asm: -------------------------------------------------------------------------------- 1 | ; =============================================================== 2 | ; --------------------------------------------------------------- 3 | ; Constants 4 | ; --------------------------------------------------------------- 5 | 6 | ; ---------------------------- 7 | ; Arguments formatting flags 8 | ; ---------------------------- 9 | 10 | ; General arguments format flags 11 | hex equ $80 ; flag to display as hexadecimal number 12 | #ifdef BUNDLE-AXM68K 13 | ## For AXM68K compatibility, we replace "dec" with "deci" 14 | deci equ $90 ; flag to display as decimal number 15 | #else 16 | dec equ $90 ; flag to display as decimal number 17 | #endif 18 | bin equ $A0 ; flag to display as binary number 19 | sym equ $B0 ; flag to display as symbol (treat as offset, decode into symbol +displacement, if present) 20 | symdisp equ $C0 ; flag to display as symbol's displacement alone (DO NOT USE, unless complex formatting is required, see notes below) 21 | str equ $D0 ; flag to display as string (treat as offset, insert string from that offset) 22 | 23 | ; NOTES: 24 | ; * By default, the "sym" flag displays both symbol and displacement (e.g.: "Map_Sonic+$2E") 25 | ; In case, you need a different formatting for the displacement part (different text color and such), 26 | ; use "sym|split", so the displacement won't be displayed until symdisp is met 27 | ; * The "symdisp" can only be used after the "sym|split" instance, which decodes offset, otherwise, it'll 28 | ; display a garbage offset. 29 | #ifdef BUNDLE-AXM68K 30 | ; * No other argument format flags (hex, deci, bin, str) are allowed between "sym|split" and "symdisp", 31 | #else 32 | ; * No other argument format flags (hex, dec, bin, str) are allowed between "sym|split" and "symdisp", 33 | #endif 34 | ; otherwise, the "symdisp" results are undefined. 35 | ; * When using "str" flag, the argument should point to string offset that will be inserted. 36 | ; Arguments format flags CAN NOT be used in the string (as no arguments are meant to be here), 37 | ; only console control flags (see below). 38 | 39 | 40 | ; Additional flags ... 41 | #ifdef BUNDLE-AXM68K 42 | ; ... for number formatters (hex, deci, bin) 43 | #else 44 | ; ... for number formatters (hex, dec, bin) 45 | #endif 46 | signed equ 8 ; treat number as signed (display + or - before the number depending on sign) 47 | 48 | ; ... for symbol formatter (sym) 49 | split equ 8 ; DO NOT write displacement (if present), skip and wait for "symdisp" flag to write it later (optional) 50 | forced equ 4 ; display "" if symbol was not found, otherwise, plain offset is displayed by the displacement formatter 51 | 52 | ; ... for symbol displacement formatter (symdisp) 53 | weak equ 8 ; DO NOT write plain offset if symbol is displayed as "" 54 | 55 | ; Argument type flags: 56 | ; - DO NOT USE in formatted strings processed by macros, as these are included automatically 57 | ; - ONLY USE when writting down strings manually with DC.B 58 | byte equ 0 59 | word equ 1 60 | long equ 3 61 | 62 | ; ----------------------- 63 | ; Console control flags 64 | ; ----------------------- 65 | 66 | ; Plain control flags: no arguments following 67 | endl equ $E0 ; "End of line": flag for line break 68 | cr equ $E6 ; "Carriage return": jump to the beginning of the line 69 | pal0 equ $E8 ; use palette line #0 70 | pal1 equ $EA ; use palette line #1 71 | pal2 equ $EC ; use palette line #2 72 | pal3 equ $EE ; use palette line #3 73 | 74 | ; Parametrized control flags: followed by 1-byte argument 75 | setw equ $F0 ; set line width: number of characters before automatic line break 76 | setoff equ $F4 ; set tile offset: lower byte of base pattern, which points to tile index of ASCII character 00 77 | setpat equ $F8 ; set tile pattern: high byte of base pattern, which determines palette flags and $100-tile section id 78 | setx equ $FA ; set x-position 79 | 80 | ; ----------------------------- 81 | ; Error handler control flags 82 | ; ----------------------------- 83 | 84 | ; Screen appearence flags 85 | _eh_address_error equ $01 ; use for address and bus errors only (tells error handler to display additional "Address" field) 86 | _eh_show_sr_usp equ $02 ; displays SR and USP registers content on error screen 87 | _eh_hide_caller equ $04 ; don't guess and print caller in the header (in SGDK and C/C++ projects naive caller detection isn't reliable) 88 | 89 | ; Advanced execution flags 90 | ; WARNING! For experts only, DO NOT USE them unless you know what you're doing 91 | _eh_return equ $20 92 | _eh_enter_console equ $40 93 | _eh_align_offset equ $80 94 | -------------------------------------------------------------------------------- /modules/errorhandler/Debugger.asm: -------------------------------------------------------------------------------- 1 | 2 | ; =============================================================== 3 | ; --------------------------------------------------------------- 4 | ; MD Debugger and Error Handler v.2.6 5 | ; 6 | ; 7 | ; Documentation, references and source code are available at: 8 | ; - https://github.com/vladikcomper/md-modules 9 | ; 10 | ; (c) 2016-2024, Vladikcomper 11 | ; --------------------------------------------------------------- 12 | ; Debugger definitions 13 | ; --------------------------------------------------------------- 14 | 15 | #include Debugger.Config.asm 16 | 17 | 18 | #include Debugger.Constants.asm 19 | 20 | #ifdef LINKABLE 21 | #ifdef BUNDLE-ASM68K 22 | ; =============================================================== 23 | ; --------------------------------------------------------------- 24 | ; Symbols imported from the object file 25 | ; --------------------------------------------------------------- 26 | 27 | #include ../../build/modules/errorhandler-core/ErrorHandler.Linkable.Refs.asm 28 | #else 29 | ## AS bundle doesn't support linkable builds! 30 | #endif 31 | #endif 32 | 33 | 34 | ; =============================================================== 35 | ; --------------------------------------------------------------- 36 | ; Macros 37 | ; --------------------------------------------------------------- 38 | 39 | #ifdef BUNDLE-ASM68K 40 | #include Debugger.Macros.ASM68K.asm 41 | #endif 42 | #ifdef BUNDLE-AXM68K 43 | #include Debugger.Macros.ASM68K.asm 44 | #endif 45 | #ifdef BUNDLE-AS 46 | #include Debugger.Macros.AS.asm 47 | #endif 48 | ## 49 | 50 | ; --------------------------------------------------------------- 51 | ; MIT License 52 | ; 53 | ; Copyright (c) 2016-2024 Vladikcomper 54 | ; 55 | ; Permission is hereby granted, free of charge, to any person 56 | ; obtaining a copy ; of this software and associated 57 | ; documentation files (the "Software"), to deal in the Software 58 | ; without restriction, including without limitation the rights 59 | ; to use, copy, modify, merge, publish, distribute, sublicense, 60 | ; and/or sell copies of the Software, and to permit persons to 61 | ; whom the Software is furnished to do so, subject to the 62 | ; following conditions: 63 | ; 64 | ; The above copyright notice and this permission notice shall be 65 | ; included in all copies or substantial portions of the Software. 66 | ; 67 | ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 68 | ; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 69 | ; OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 70 | ; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 71 | ; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 72 | ; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 73 | ; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 74 | ; OTHER DEALINGS IN THE SOFTWARE. 75 | ; --------------------------------------------------------------- 76 | -------------------------------------------------------------------------------- /modules/errorhandler/ErrorHandler-GAS.asm: -------------------------------------------------------------------------------- 1 | 2 | .section .text 3 | .align 2 4 | 5 | * =============================================================== 6 | * --------------------------------------------------------------- 7 | * MD Debugger and Error Handler v.2.6 8 | * 9 | * (c) 2016-2024, Vladikcomper 10 | * --------------------------------------------------------------- 11 | * Error handler blob (GNU AS version) 12 | * --------------------------------------------------------------- 13 | 14 | __ErrorHandler: 15 | #include ../../build/modules/errorhandler-core/ErrorHandler.GAS.Blob.asm 16 | 17 | * --------------------------------------------------------------- 18 | * Exported symbols 19 | * --------------------------------------------------------------- 20 | 21 | #include ../../build/modules/errorhandler-core/ErrorHandler.GAS.Refs.asm 22 | 23 | #include ../../build/modules/errorhandler-core/ErrorHandler.GAS.Globals.asm 24 | 25 | * --------------------------------------------------------------- 26 | * MIT License 27 | * 28 | * Copyright (c) 2016-2024 Vladikcomper 29 | * 30 | * Permission is hereby granted, free of charge, to any person 31 | * obtaining a copy ; of this software and associated 32 | * documentation files (the "Software"), to deal in the Software 33 | * without restriction, including without limitation the rights 34 | * to use, copy, modify, merge, publish, distribute, sublicense, 35 | * and/or sell copies of the Software, and to permit persons to 36 | * whom the Software is furnished to do so, subject to the 37 | * following conditions: 38 | * 39 | * The above copyright notice and this permission notice shall be 40 | * included in all copies or substantial portions of the Software. 41 | * 42 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 43 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 44 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 45 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 46 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 47 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 48 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 49 | * OTHER DEALINGS IN THE SOFTWARE. 50 | * --------------------------------------------------------------- 51 | -------------------------------------------------------------------------------- /modules/errorhandler/ErrorHandler.Exceptions.asm: -------------------------------------------------------------------------------- 1 | 2 | ; --------------------------------------------------------------- 3 | ; Exception vectors 4 | ; --------------------------------------------------------------- 5 | 6 | if DEBUGGER__SHOW_SR_USP 7 | _eh_default: equ _eh_show_sr_usp 8 | else 9 | _eh_default: equ 0 10 | endif 11 | 12 | ; --------------------------------------------------------------- 13 | 14 | BusError: 15 | __ErrorMessage "BUS ERROR", _eh_default|_eh_address_error 16 | 17 | AddressError: 18 | __ErrorMessage "ADDRESS ERROR", _eh_default|_eh_address_error 19 | 20 | IllegalInstr: 21 | __ErrorMessage "ILLEGAL INSTRUCTION", _eh_default 22 | 23 | ZeroDivide: 24 | __ErrorMessage "ZERO DIVIDE", _eh_default 25 | 26 | ChkInstr: 27 | __ErrorMessage "CHK INSTRUCTION", _eh_default 28 | 29 | TrapvInstr: 30 | __ErrorMessage "TRAPV INSTRUCTION", _eh_default 31 | 32 | PrivilegeViol: 33 | __ErrorMessage "PRIVILEGE VIOLATION", _eh_default 34 | 35 | Trace: 36 | __ErrorMessage "TRACE", _eh_default 37 | 38 | Line1010Emu: 39 | __ErrorMessage "LINE 1010 EMULATOR", _eh_default 40 | 41 | Line1111Emu: 42 | __ErrorMessage "LINE 1111 EMULATOR", _eh_default 43 | 44 | ErrorExcept: 45 | __ErrorMessage "ERROR EXCEPTION", _eh_default -------------------------------------------------------------------------------- /modules/errorhandler/ErrorHandler.asm: -------------------------------------------------------------------------------- 1 | 2 | ; =============================================================== 3 | ; --------------------------------------------------------------- 4 | ; MD Debugger and Error Handler v.2.6 5 | ; 6 | ; 7 | ; Documentation, references and source code are available at: 8 | ; - https://github.com/vladikcomper/md-modules 9 | ; 10 | ; (c) 2016-2024, Vladikcomper 11 | ; --------------------------------------------------------------- 12 | ; Debugger and Error handler blob 13 | ; --------------------------------------------------------------- 14 | 15 | #include ErrorHandler.Exceptions.asm 16 | 17 | 18 | ; --------------------------------------------------------------- 19 | ; MD Debugger blob 20 | ; --------------------------------------------------------------- 21 | 22 | ErrorHandler: 23 | #ifdef DEBUG 24 | #include ../../build/modules/errorhandler-core/ErrorHandler.Debug.Blob.asm 25 | #else 26 | #ifdef EXTSYM 27 | #include ../../build/modules/errorhandler-core/ErrorHandler.ExtSymbols.Blob.asm 28 | #else 29 | #include ../../build/modules/errorhandler-core/ErrorHandler.Blob.asm 30 | #endif 31 | #endif 32 | 33 | ; --------------------------------------------------------------- 34 | ; MD Debugger's exported symbols 35 | ; --------------------------------------------------------------- 36 | 37 | #ifdef EXTSYM 38 | #include ../../build/modules/errorhandler-core/ErrorHandler.ExtSymbols.Globals.asm 39 | #else 40 | #ifdef DEBUG 41 | #include ../../build/modules/errorhandler-core/ErrorHandler.Debug.Globals.asm 42 | #else 43 | #include ../../build/modules/errorhandler-core/ErrorHandler.Globals.asm 44 | #endif 45 | #endif 46 | 47 | #ifndef EXTSYM 48 | ; --------------------------------------------------------------- 49 | ; WARNING! 50 | ; DO NOT put any data from now on! DO NOT use ROM padding! 51 | ; Symbol data should be appended here after ROM is compiled 52 | ; by ConvSym utility, otherwise debugger modules won't be able 53 | ; to resolve symbol names. 54 | ; --------------------------------------------------------------- 55 | #endif 56 | -------------------------------------------------------------------------------- /modules/errorhandler/docs/.images/blastem-win-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/errorhandler/docs/.images/blastem-win-console.png -------------------------------------------------------------------------------- /modules/errorhandler/docs/.images/eh_address_error_v.2.6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/errorhandler/docs/.images/eh_address_error_v.2.6.png -------------------------------------------------------------------------------- /modules/errorhandler/docs/.images/eh_address_regs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/errorhandler/docs/.images/eh_address_regs.png -------------------------------------------------------------------------------- /modules/errorhandler/docs/.images/eh_assertion_v.2.6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/errorhandler/docs/.images/eh_assertion_v.2.6.png -------------------------------------------------------------------------------- /modules/errorhandler/docs/.images/eh_backtrace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/errorhandler/docs/.images/eh_backtrace.png -------------------------------------------------------------------------------- /modules/errorhandler/docs/.images/eh_custom_debugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/errorhandler/docs/.images/eh_custom_debugger.png -------------------------------------------------------------------------------- /modules/errorhandler/docs/.images/eh_illegal_instr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/errorhandler/docs/.images/eh_illegal_instr.png -------------------------------------------------------------------------------- /modules/errorhandler/docs/.images/eh_kdebug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/errorhandler/docs/.images/eh_kdebug.png -------------------------------------------------------------------------------- /modules/errorhandler/docs/.images/gens-kmod_debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/errorhandler/docs/.images/gens-kmod_debug.png -------------------------------------------------------------------------------- /modules/errorhandler/docs/.images/gens-kmod_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/errorhandler/docs/.images/gens-kmod_menu.png -------------------------------------------------------------------------------- /modules/errorhandler/docs/.images/gens-kmod_messages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/errorhandler/docs/.images/gens-kmod_messages.png -------------------------------------------------------------------------------- /modules/errorhandler/docs/how-to/Modify_exception_header.md: -------------------------------------------------------------------------------- 1 | 2 | # How-to add your details in exception headers 3 | 4 | This quick guide shows how to append your own messages to the standard exception headers. This is useful for leaving user instructions and contact information for reporting bugs. 5 | 6 | ## Compatibility 7 | 8 | This guide applies to any project, disassembly and any version of MD Debugger (both AS and ASM68K). 9 | 10 | ## Instructions 11 | 12 | ### Step 1. Setting up a header extension 13 | 14 | 1. Open `ErrorHandler.asm` file and locate the following lines: 15 | 16 | ```m68k 17 | BusError: 18 | __ErrorMessage "BUS ERROR", _eh_default|_eh_address_error 19 | 20 | AddressError: 21 | __ErrorMessage "ADDRESS ERROR", _eh_default|_eh_address_error 22 | 23 | IllegalInstr: 24 | __ErrorMessage "ILLEGAL INSTRUCTION", _eh_default 25 | 26 | ZeroDivide: 27 | __ErrorMessage "ZERO DIVIDE", _eh_default 28 | 29 | ChkInstr: 30 | __ErrorMessage "CHK INSTRUCTION", _eh_default 31 | 32 | TrapvInstr: 33 | __ErrorMessage "TRAPV INSTRUCTION", _eh_default 34 | 35 | PrivilegeViol: 36 | __ErrorMessage "PRIVILEGE VIOLATION", _eh_default 37 | 38 | Trace: 39 | __ErrorMessage "TRACE", _eh_default 40 | 41 | Line1010Emu: 42 | __ErrorMessage "LINE 1010 EMULATOR", _eh_default 43 | 44 | Line1111Emu: 45 | __ErrorMessage "LINE 1111 EMULATOR", _eh_default 46 | 47 | ErrorExcept: 48 | __ErrorMessage "ERROR EXCEPTION", _eh_default 49 | ``` 50 | 51 | 2. We're going to create a custom C-string that will be appended to every exception invocation listed above. To do that insert the following somewhere **before** the frament above: 52 | 53 | ```m68k 54 | Str_ErrorHeader: 55 | dc.b "Dummy common header", endl, endl 56 | dc.b 0 57 | ``` 58 | 59 | 3. Now, let's make use of `Str_ErrorHeader`. Append all exception messages from step 1 with `%<.l #Str_ErrorHeader str>`. You should end up with the following code: 60 | 61 | ```m68k 62 | BusError: 63 | __ErrorMessage "%<.l #Str_ErrorHeader str>BUS ERROR", _eh_default|_eh_address_error 64 | 65 | AddressError: 66 | __ErrorMessage "%<.l #Str_ErrorHeader str>ADDRESS ERROR", _eh_default|_eh_address_error 67 | 68 | IllegalInstr: 69 | __ErrorMessage "%<.l #Str_ErrorHeader str>ILLEGAL INSTRUCTION", _eh_default 70 | 71 | ZeroDivide: 72 | __ErrorMessage "%<.l #Str_ErrorHeader str>ZERO DIVIDE", _eh_default 73 | 74 | ChkInstr: 75 | __ErrorMessage "%<.l #Str_ErrorHeader str>CHK INSTRUCTION", _eh_default 76 | 77 | TrapvInstr: 78 | __ErrorMessage "%<.l #Str_ErrorHeader str>TRAPV INSTRUCTION", _eh_default 79 | 80 | PrivilegeViol: 81 | __ErrorMessage "%<.l #Str_ErrorHeader str>PRIVILEGE VIOLATION", _eh_default 82 | 83 | Trace: 84 | __ErrorMessage "%<.l #Str_ErrorHeader str>TRACE", _eh_default 85 | 86 | Line1010Emu: 87 | __ErrorMessage "%<.l #Str_ErrorHeader str>LINE 1010 EMULATOR", _eh_default 88 | 89 | Line1111Emu: 90 | __ErrorMessage "%<.l #Str_ErrorHeader str>LINE 1111 EMULATOR", _eh_default 91 | 92 | ErrorExcept: 93 | __ErrorMessage "%<.l #Str_ErrorHeader str>ERROR EXCEPTION", _eh_default 94 | ``` 95 | 96 | 4. Now build your ROM to make sure everything is set correctly. The easiest way to test the change is to use `illegal` instruction anywhere in your code. Once it executes, you should see the extended header in your screen. 97 | 98 | ### Step 2. Customize your text 99 | 100 | Now it's time to make this placeholder message fancier! 101 | 102 | You can use the following special bytes to format it: 103 | 104 | - `endl` - Starts a new line 105 | - `pal0` - Use palette line #0 (white) 106 | - `pal1` - Use palette line #1 (yellow) 107 | - `pal2` - Use palette line #2 (blue) 108 | - `pal3` - Use palette line #3 (dark-blue) 109 | 110 | Here's an example of various flags in action: 111 | 112 | ```m68k 113 | Str_ErrorHeader: 114 | dc.b pal0,"This hack is experiencing technical", endl 115 | dc.b "difficulties, please stand by~", endl, endl 116 | dc.b pal3,"Please send the following information", endl 117 | dc.b "to ",pal2,"megahacker777@mail.kool",pal3,":",pal0,endl,endl 118 | dc.b 0 119 | even 120 | ``` 121 | 122 | Notice how this also modifies the color of actual exception message. 123 | 124 | Try out various options and choose whatever fulfils you needs! 125 | -------------------------------------------------------------------------------- /modules/errorhandler/docs/how-to/Troubleshoot.md: -------------------------------------------------------------------------------- 1 | 2 | # MD-Debugger Troubleshooting 3 | 4 | ## Problem: I can't see any symbols in exceptions, just raw offsets 5 | 6 | In 99,99% of cases this means the Debugger is not able to find symbols table because it wasn't included or was placed incorrectly. 7 | 8 | **Things to check out:** 9 | 10 | - Do you call `convsym` utility in your build scripts? Is it invoked correctly? 11 | - Make sure you don't have anything included after `include "ErrorHandler.asm"` in your code, not even a single byte; 12 | - Make sure you don't pad your ROM **before** appending the symbol table with `convsym`. Remember that the symbol table must be appended directly after the Error Handler (`include "ErrorHandler.asm"`). 13 | 14 | ## Problem: I use SRAM, my ROM is big and Error Handler seems to glitch out 15 | 16 | SRAM starts at offset `$200000`, this is after the second megabyte of your ROM. If SRAM access is enabled, it effectively remaps all the memory accesses after offset `$200000` from ROM to SRAM. 17 | 18 | This becomes an issue if your ROM is larger than 2 MiB. Since the error handler and debug symbol table are included at the very end, they are affected first. 19 | 20 | You need to enable SRAM access only when you need to write anything to SRAM and disable it right after. If you're unsure, check out how S3K does it in the disassembly: it's 4 MiB game, so it has to be really careful with SRAM access. 21 | 22 | ## Problem: `assert` and `KDebug` macros have no effect 23 | 24 | `assert` and `KDebug` macros are only compiled in DEBUG builds. It's when `__DEBUG__` symbol is defined. 25 | 26 | If you followed the installation instructions, you should have two builds of your ROM: RELEASE (`.bin`) and DEBUG (`.debug.bin`). `assert` and `KDebug` macros only generate code in DEBUG builds and are just ignored in RELEASE ones. 27 | 28 | If you have troubles testing `KDebug` macros in DEBUG builds, make sure you use the supported emulator. See the [Using KDebug integration](Use_KDebug_integration.md) guide. 29 | 30 | ## Problem: Sometimes when I use `assert` or `KDebug` macros, I get "illegal zero-length branch" errors 31 | 32 | This is because `assert` and `KDebug` don't generate any code in RELEASE builds. Consider the following example: 33 | 34 | ```m68k 35 | MyFunction: 36 | tst.b SomeFlag 37 | beq.s @skip 38 | KDebug.WriteLine "Calling MyFunction when SomeFlag is set!" 39 | @skip: 40 | ``` 41 | 42 | In RELEASE builds (when `__DEBUG__` symbol is not set), the `KDebug.WriteLine` invocation effectively turns into nothing: 43 | 44 | ```m68k 45 | MyFunction: 46 | tst.b SomeFlag 47 | beq.s @skip 48 | @skip: 49 | ``` 50 | 51 | This, in turn, causes assembler to fail with "Illegal zero-length branch" error because `beq.s` cannot jump to the very next line (`beq.w` can, on the other hand). 52 | 53 | **The easiest way** to avoid this is to add a dummy `nop` before your macro invocation in this case. 54 | 55 | **A proper way** to fix this is to compile the branch itself in DEBUG builds only (if it's only used for debugging): 56 | 57 | ```m68k 58 | MyFunction: 59 | if def(__DEBUG__) ; ifdef __DEBUG__ in AS 60 | tst.b SomeFlag 61 | beq.s @skip 62 | KDebug.WriteLine "Calling MyFunction when SomeFlag is set!" 63 | @skip: 64 | endif 65 | ``` 66 | 67 | ## Problem: Using `KDebug` or `Console` alongside VRAM writes has side effects 68 | 69 | Be extremely careful when using `KDebug` or `Console.Write`/`.WriteLine`/`.BreakLine` to debug code that does VRAM/CRAM/VSRAM access. All these macros access VDP, so it resets the last accessed VRAM/CRAM/VSRAM address. Moreover, `Console.Write` calls set last write address to on-screen output so your code may output data to the screen instead of the intended location. 70 | 71 | This is a hardware limitation unfortunately, there isn't a universal workaround for this. 72 | 73 | To make debugging VDP writes as pain-free as possible, follow this advice: 74 | - Make sure you don't call `KDebug` or `Console` macros in-between VDP data port writes. 75 | - If you absolutely have to, manually set VDP write address after each macro call. 76 | -------------------------------------------------------------------------------- /modules/errorhandler/docs/how-to/Use_KDebug_integration.md: -------------------------------------------------------------------------------- 1 | 2 | # Using KDebug integration in MD Debugger 3 | 4 | ![KDebug integration](../.images/eh_kdebug.png) 5 | 6 | MD Debugger version 2.5 and above supports "KDebug" integration, which allows you to log messages, count cycles and add breakpoints in emulators that support it. This is achieved by using `KDebug` macros (e.g. `KDebug.WriteLine`, `KDebug.BreakPoint`). Please note that these macros only have effect in DEBUG builds (when `__DEBUG__` equate is set). 7 | 8 | Currently, the only emulators that support KDebug are: 9 | - Blastem-nightly (recommended); 10 | - Clownmdemu v.0.8 and above (logging only); 11 | - Gens KMod (outdated, not recommended). 12 | 13 | This guide briefly shows you how to set up both emulators to work with KDebug. 14 | 15 | ## Adding logging in your game 16 | 17 | Before we actually set up one of the supported emulators, we need to actually make use of "KDebug" integration in the code, so we have something to tests. 18 | 19 | The most commonly used (and overall useful) function is `KDebug.WriteLine`, which logs a formatted string to emulator's own console/window. 20 | 21 | You can put the following anywhere in your code. Just make sure this code is reached when you test it: 22 | 23 | ```m68k 24 | KDebug.WriteLine "Hello, world!" 25 | ``` 26 | 27 | For a sligthly more practical example, you may also check "Log in-game events with `KDebug`" section of [Debugging Techniques](Debugging_techniques.md) guide. 28 | 29 | For a full reference of the available `KDebug` macros, see the [Macros Reference](../Debug_macros.md). 30 | 31 | ## Setting up Blastem-nightly (recommended) 32 | 33 | Newer, not yet officially released versions of Blastem support KDebug integration. They are available as nightly builds, hence why I used the name "Blastem-nightly". 34 | 35 | You can find all nightly builds here: https://www.retrodev.com/blastem/nightlies/. Make sure to get Blastem version 0.6.3-pre for your platform that was build recently. 36 | 37 | At the time of writing, the following versions were up-to date: 38 | - Windows 64-bit: https://www.retrodev.com/blastem/nightlies/blastem-win64-0.6.3-pre-f973651b48d7.zip 39 | - Linux 64-bit: https://www.retrodev.com/blastem/nightlies/blastem64-0.6.3-pre-f973651b48d7.tar.gz 40 | 41 | ### Viewing debug messages 42 | 43 | Once your ROM any KDebug log message, a terminal window should appear with the logged message displayed. 44 | 45 | On Linux though, the output in the terminal window can be delayed and only flushed in chunks (as of tested builds), so it's recommended to run Blastem from the terminal instead (this way KDebug logs are redirected to standard output): 46 | 47 | 1. Open Terminal and type in path to the `blastem` executable (you may add it to your `PATH` for easier access); 48 | 2. You will see debug messages in the very same terminal. 49 | 50 | If you have issues seing Blastem's terminal output on your OS, consider the following: 51 | 52 | 1. Press the `u` key to pause the game and toggle debugger; 53 | 2. You should now emulator's console and all the messages up to this point; 54 | 3. Since you've paused the game by toggling the debugger, type `c` in the console and press `Enter` to continue; 55 | 4. The emulator's console will still be open in a separate window and you should now see debug messages properly. 56 | 57 | ![Blastem console on Windows](../.images/blastem-win-console.png) 58 | 59 | ## Setting up Clownmdemu 60 | 61 | Clownmdemu has partial support for KDebug integration since v.0.8. It only allows for KDebug logging (meaning only `KDebug.WriteLine` macros work). 62 | 63 | Setting it up is quite easy: open **Debugging > Log** in the menu and check "Enable Logging". KDebug logging will only be enabled after it, so if you wanted any logging during ROM's boot, you may want to soft-reset. 64 | 65 | ## Setting up Gens KMod (not recommended) 66 | 67 | > [!WARNING] 68 | > 69 | > Kens KMod is a heavily outdated and inaccurate emulator. **Using it is not recommended.** 70 | 71 | 1. Start the emulator and enable debugging: "Option" > "Debug..." > "Active Development Features". 72 | 73 | ![Gens KMod Debug menu](../.images/gens-kmod_menu.png) 74 | 75 | ![Gens KMod Debug options](../.images/gens-kmod_debug.png) 76 | 77 | 2. Open "CPU" > "Debug" > "Messages" to see the logs. 78 | 79 | ![Gens KMod Debug Messages](../.images/gens-kmod_messages.png) 80 | -------------------------------------------------------------------------------- /modules/errorhandler/docs/installation/Sonic_1_Hivebrain_2022.md: -------------------------------------------------------------------------------- 1 | 2 | # Installing MD Debugger in Sonic 1 Hivebrain 2022 Disassembly 3 | 4 | This guide describes a step-by-step installation process of the MD Error Handler and Debugger 2.5 and above in Sonic 1 Hivebrain 2022 disassembly. 5 | 6 | The base disassembly used for this installation is available here: https://github.com/cvghivebrain/s1disasm 7 | 8 | > [!NOTE] 9 | > 10 | > You want a similar disassembly as a base, you can also try **Sonic 1-squared** disassembly, which already comes with MD Debugger and Error Handler pre-installed: https://github.com/cvghivebrain/Sonic1sq 11 | 12 | ## Step 1. Download and unpack the debugger 13 | 14 | 1. Open the release page for the recent version of MD Debugger on GitHub: https://github.com/vladikcomper/md-modules/releases/tag/v.2.6-mddebugger 15 | 2. Download `errorhandler-2.6.zip` from this page, find `axm68k` directory inside; 16 | 3. Extract files from the `axm68k` directory (AXM68K bundle) into disassembly's root directory. 17 | 18 | ## Step 2. Include debugger macros in your disassembly 19 | 20 | Open `_Main.asm` file in your favorite text editor and add the following somewhere at the beginning (e.g. above `include "Macros.asm"`): 21 | 22 | ```m68k 23 | include "Debugger.asm" 24 | ``` 25 | 26 | Don't try to build the ROM just yet. We're still missing one important component: the Error Handler itself! 27 | 28 | ## Step 3. Install the Error Handler 29 | 30 | The `Debugger.asm` file you've included earlier is just a set of macros definitions. You cannot actually use them unless there's real code that handles exceptions, debug text rendering etc. So you must now include the Error Handler itself (the `ErrorHandler.asm` file that you've also copied). 31 | 32 | > [!NOTE] 33 | > 34 | > Why cannot we just have both definitions and code in one file? It's because we need Error Handler's code at the very end of the ROM, but debugger macros should be available from the beginning. In C/C++ terms, one is a header, and the other is an actual object file that is linked (included) the last. While it's technically possible to include Error Handler anywhere after the ROM header (e.g. in the middle), it's absolutely required that the debug symbol table is included after it, which is only possible at the end of ROM. 35 | 36 | 1. At the very bottom of `_Main.asm`, just before `ROM_End:`, add the following snippet: 37 | 38 | ```m68k 39 | ; ============================================================== 40 | ; -------------------------------------------------------------- 41 | ; Debugging modules 42 | ; -------------------------------------------------------------- 43 | 44 | include "ErrorHandler.asm" 45 | 46 | ; -------------------------------------------------------------- 47 | ; WARNING! 48 | ; DO NOT put any data from now on! DO NOT use ROM padding! 49 | ; Symbol data should be appended here after ROM is compiled 50 | ; by ConvSym utility, otherwise debugger modules won't be able 51 | ; to resolve symbol names. 52 | ; -------------------------------------------------------------- 53 | ``` 54 | 55 | This includes entry points for exception vectors (Illegal instruction, Address Error etc), error handler configuration and the blob itself along with extensions (all inlined in the assembly file). 56 | 57 | The "WARNING!" comment in the snippet is just so you don't forget that _you cannot put anything_ after `include "ErrorHandler.asm"`, otherwise debug symbols cannot be used, because they will be appended just after the end of the ROM by ConvSym utility. 58 | 59 | 2. If you try to run `build.bat` now, you'll find quite a few errors related to multiply-defined labels. This is because the new error handler now conflicts with Sonic 1's native one. Fixing this is pretty straight-forward. Just remove the old code. 60 | 61 | In `_Main.asm`, find and remove the following line: 62 | 63 | ```m68k 64 | include "Includes\Errors.asm" 65 | ``` 66 | 67 | This remoeves the original error handler. Let's also `Includes\Errors.asm` file as well, since we don't need it. 68 | 69 | 3. After removing the old exceptions code, run `build.bat` and make sure your ROM builds properly. 70 | 71 | Once everything's done, congratulations, the Error Handler is installed, you're almost there! 72 | 73 | 74 | ## Step 4. Install ConvSym to generate debug symbols 75 | 76 | > [!WARNING] 77 | > 78 | > This section is slightly incomplete, because it doesn't introduce DEBUG builds for features like KDebug and assertions. 79 | 80 | 1. Go back to the release page for the recent version of MD Debugger on GitHub: https://github.com/vladikcomper/md-modules/releases/tag/v.2.6-mddebugger 81 | 82 | 2. Download the ConvSym utility for Windows (or your current platform, e.g. Linux, FreeBSD, MacOS); 83 | 84 | 3. Extract `convsym.exe` to `bin/` directory in the disassembly. 85 | 86 | 4. Now, open `build.bat` and locate the following line: 87 | 88 | ```shell 89 | "bin\axm68k.exe" /m /k /p _Main.asm, s1built.bin >errors.txt, , _Main.lst 90 | ``` 91 | 92 | 5. Replace all the lines above with the following code: 93 | 94 | ```shell 95 | "bin\axm68k.exe" /m /k /p _Main.asm, s1built.bin >errors.txt, _Main.sym, _Main.lst 96 | "bin\convsym.exe" _Main.sym s1built.bin -a 97 | ``` 98 | 99 | > [!NOTE] 100 | > 101 | > ASM68K compiles code in _case-insensitive mode_ by default. This means all symbols will be converted to lower-case. If you want to preserve case, add `/o c+` to compile flags to enable the _case-sensitive mode_. Beware that you may need to fix a lot of labels if their casing differs! 102 | 103 | That's it! Save `build.bat` and run it. Make sure the are no errors in the output. 104 | -------------------------------------------------------------------------------- /modules/errorhandler/tests/addressing-modes.asm: -------------------------------------------------------------------------------- 1 | 2 | ; =============================================================== 3 | ; --------------------------------------------------------------- 4 | ; Error handling and debugging modules 5 | ; 2016-2024, Vladikcomper 6 | ; --------------------------------------------------------------- 7 | ; Test M68K addressing modes covered by macros 8 | ; --------------------------------------------------------------- 9 | 10 | __DEBUG__: equ 1 ; enable KDebug 11 | 12 | include "..\..\..\build\modules\mdshell\headless\MDShell.asm" 13 | 14 | if __ASM68K__ 15 | opt l+ ; use "." for local labels for AS compatibility 16 | 17 | include "..\..\..\build\modules\errorhandler\asm68k-debug\Debugger.asm" 18 | else 19 | include "..\..\..\build\modules\errorhandler\as\Debugger.asm" 20 | endif 21 | 22 | ; -------------------------------------------------------------- 23 | Main: 24 | Console.SetXY #1, #1 25 | Console.WriteLine "%TESTS PASS IF VALUES IN BRACKETS MATCH%%" 26 | 27 | moveq #2, d0 28 | lea (DecValues).w, a0 29 | 30 | Console.WriteLine "D0: $%<.w d0> ($0002)" 31 | Console.WriteLine "A0: %<.w a0 sym> (DecValues)" 32 | Console.WriteLine "(A0): %<.w (a0) dec> (1234)" 33 | Console.WriteLine "2(a0): %<.w 2(a0) dec> (5678)" 34 | Console.WriteLine "X(pc): %<.w DecValues(pc) dec> (1234)" 35 | Console.WriteLine "X: %<.w DecValues dec> (1234)" 36 | Console.WriteLine "X.w: %<.w DecValues.w dec> (1234)" 37 | Console.WriteLine "X.l: %<.w DecValues.w dec> (1234)" 38 | Console.WriteLine "(X).w: %<.w (DecValues).w dec> (1234)" 39 | Console.WriteLine "(X).l: %<.w (DecValues).l dec> (1234)" 40 | Console.WriteLine "#X: %<.w #42 dec> (42)" 41 | 42 | ; The following modes are supported only by ASM68K bundles 43 | if __ASM68K__ 44 | var0: equr d0 45 | Console.WriteLine "D0 (alias): $%<.w var0> ($0002)" 46 | Console.WriteLine "(A0)+: %<.w (a0)+ dec> (1234)" 47 | Console.WriteLine "-(A0): %<.w -(a0) dec> (1234)" 48 | Console.WriteLine "0(a0,d0): %<.w 0(a0,var0) dec> (5678)" 49 | Console.WriteLine "X(pc,d0): %<.w DecValues(pc,var0) dec> (5678)" 50 | endif 51 | rts 52 | 53 | DecValues: 54 | dc.w 1234 55 | dc.w 5678 56 | 57 | ; -------------------------------------------------------------- 58 | 59 | if __ASM68K__=1 60 | include "..\..\..\build\modules\errorhandler\asm68k-debug\ErrorHandler.asm" 61 | else 62 | include "..\..\..\build\modules\errorhandler\as\ErrorHandler.asm" 63 | endif 64 | -------------------------------------------------------------------------------- /modules/errorhandler/tests/asm68k-dot-compat.asm: -------------------------------------------------------------------------------- 1 | 2 | ; =============================================================== 3 | ; --------------------------------------------------------------- 4 | ; Error handling and debugging modules 5 | ; 6 | ; (c) 2016-2024, Vladikcomper 7 | ; --------------------------------------------------------------- 8 | ; ASM68K "dot for local labels" macro compatibility test 9 | ; --------------------------------------------------------------- 10 | 11 | section rom 12 | opt l+ ; use "." for local labels 13 | 14 | xdef SymbolData_Ptr 15 | 16 | include "..\..\..\build\modules\mdshell\headless\MDShell.asm" 17 | 18 | include "..\..\..\build\modules\errorhandler\asm68k-linkable\Debugger.asm" 19 | 20 | ; -------------------------------------------------------------- 21 | 22 | SymbolData_Ptr: equ $200 23 | 24 | ; -------------------------------------------------------------- 25 | 26 | Main: 27 | nop 28 | 29 | .localRef1: 30 | KDebug.WriteLine "Hello, world! (KDebug device)" 31 | RaiseError "Dummy exception to test a local symbol for debugger", .localDebugger 32 | 33 | ; -------------------------------------------------------------- 34 | .localDebugger: 35 | assert.b SymbolData_Ptr, eq 36 | 37 | Console.WriteLine "Hello, world! (Console device)" 38 | 39 | Console.Sleep #0 40 | 41 | Console.WriteLine "Local label test: %<.l #.localRef1 sym>" 42 | bra.s .localJump 43 | 44 | KDebug.WriteLine "YOU SHOULD NEVER SEE THIS (SPACE FILLER)" 45 | 46 | .localJump: 47 | KDebug.WriteLine "Local reference test: %<.w .localRef2 dec> (must be 1234)" 48 | assert.w .localRef2, eq, #1234 49 | bra.s GlobalLabel 50 | 51 | .localRef2: 52 | dc.w 1234 53 | 54 | GlobalLabel: 55 | Console.WriteLine "ALL DONE" 56 | 57 | .localRef1: 58 | rts 59 | -------------------------------------------------------------------------------- /modules/errorhandler/tests/assertions.asm: -------------------------------------------------------------------------------- 1 | #include cbundle_disclaimer 2 | 3 | ; =============================================================== 4 | ; --------------------------------------------------------------- 5 | ; Error handling and debugging modules 6 | ; 2016-2017, Vladikcomper 7 | ; --------------------------------------------------------------- 8 | ; Assertion tests 9 | ; --------------------------------------------------------------- 10 | 11 | __DEBUG__: equ 1 ; enable KDebug and assertions 12 | 13 | include "..\..\..\build\modules\mdshell\headless\MDShell.asm" 14 | 15 | #ifdef ASM68K 16 | opt l+ ; use "." for local labels for AS compatibility 17 | 18 | include "..\..\..\build\modules\errorhandler\asm68k-debug\Debugger.asm" 19 | #else 20 | include "..\..\..\build\modules\errorhandler\as\Debugger.asm" 21 | #endif 22 | 23 | ; -------------------------------------------------------------- 24 | 25 | Main: 26 | ; Initialize registers with pseudo-random values, 27 | ; found at "RegisterData" byte-array (see below) 28 | movem.l RegisterData(pc), d0-a6 29 | 30 | Console.Run TestProgram 31 | 32 | ; -------------------------------------------------------------- 33 | TestProgram: 34 | move.w #0, ccr ; clear CCR 35 | 36 | assert.l d0, eq, RegisterData 37 | jsr CheckRegisterIntergity(pc) 38 | 39 | assert.b d0, ne 40 | jsr CheckRegisterIntergity(pc) 41 | 42 | assert.w d1, pl, , UnreachableDebugger 43 | jsr CheckRegisterIntergity(pc) 44 | 45 | assert.w .localData, eq, #$1234 46 | jsr CheckRegisterIntergity(pc) 47 | 48 | assert.w .localData, eq, #$1234, UnreachableDebugger 49 | jsr CheckRegisterIntergity(pc) 50 | 51 | assert.w .localData, ne, , UnreachableDebugger 52 | jsr CheckRegisterIntergity(pc) 53 | 54 | assert.l .reallyLongLabelIHopeItWontTriggerSomeErrorsOrSmth, eq, #$BADBEEF, ReachableDebugger 55 | 56 | Console.WriteLine "The last assertion should've failed!" 57 | Console.Write "%TESTS FAILED" 58 | rts 59 | 60 | .localData: 61 | dc.w $1234 62 | 63 | .reallyLongLabelIHopeItWontTriggerSomeErrorsOrSmth: 64 | dc.l 0 65 | 66 | ; -------------------------------------------------------------- 67 | UnreachableDebugger: 68 | Console.WriteLine "This debugger should've been unreachable!" 69 | Console.Write "%TESTS FAILED" 70 | rts 71 | 72 | ; This is to make sure sure ASM68K's local label flag is intact 73 | ; If it isn't, this'll become a global label and raise a 74 | ; duplicate label error 75 | .localData: 76 | dc.w 0 77 | 78 | ; -------------------------------------------------------------- 79 | ReachableDebugger: 80 | Console.WriteLine "%TESTS PASSED IS HEADER ABOVE READS:%Got: 00000000" 81 | rts 82 | 83 | ; -------------------------------------------------------------- 84 | ; Subroutine that check if current register values match 85 | ; array they were initialized with ... 86 | ; -------------------------------------------------------------- 87 | 88 | CheckRegisterIntergity: 89 | move.w sr, -(sp) 90 | tst.b 1(sp) ; CCR must be zero 91 | bne.s .ccr_polluted ; if not, branch 92 | movem.l d0-a6, -(sp) 93 | 94 | lea (sp), a0 ; a0 = registers dump pointer 95 | lea RegisterData(pc), a1 ; a1 = source registers pointer 96 | moveq #15-1, d0 ; d0 = number of registers minus 1 97 | 98 | .loop: 99 | cmpm.l (a0)+, (a1)+ 100 | dbne d0, .loop 101 | bne.s .corrupted 102 | movem.l (sp)+, d0-a6 103 | move.w (sp)+, sr 104 | rts 105 | 106 | ; -------------------------------------------------------------- 107 | .ccr_polluted: 108 | Console.Write "%%CCR polluted!" 109 | bra.s * 110 | 111 | ; -------------------------------------------------------------- 112 | .corrupted: 113 | subq.w #4, a0 114 | subq.w #4, a1 115 | lea RegisterNames-RegisterData(a1), a2 116 | lea $3C(sp), a3 117 | 118 | #ifdef ASM68K 119 | Console.Write "%@%<.l (a3) sym|split>: % Register %%<.l a2 str>% corrupted!% Got %%<.l (a0)>%, expected %%<.l (a1)>%" 120 | #else 121 | ## ; AS assembler implementation has limitations on formatting capabilities 122 | ## ; Moreover, AS is incapable of parsing stings that include more than 160 characters 123 | Console.Write "%%@%<.l (a3) sym|split>: %% Register %%<.l a2 str>% corrupted!" 124 | Console.Write "% Got %%<.l (a0)>%, expected %%<.l (a1)>%%" 125 | #endif 126 | bra * 127 | 128 | ; -------------------------------------------------------------- 129 | RegisterData: 130 | dc.b $47, $2F, $74, $1E, $5B, $57, $06, $22, $38, $3D, $52, $9E, $AF, $4F 131 | dc.b $BD, $96, $F0, $8A, $3B, $BB, $B9, $E2, $96, $B0, $6E, $26, $FB, $C1 132 | dc.b $6D, $21, $D0, $06, $41, $04, $0E, $FD, $93, $72, $92, $32, $E0, $AB 133 | dc.b $2F, $77, $70, $A8, $76, $B6, $0F, $3C, $6D, $7C, $70, $72, $B5, $AA 134 | dc.b $5A, $CB, $DC, $F9 135 | 136 | RegisterNames: 137 | dc.w 'd0',0,'d1',0,'d2',0,'d3',0,'d4',0,'d5',0,'d6',0,'d7',0 138 | dc.w 'a0',0,'a1',0,'a2',0,'a3',0,'a4',0,'a5',0,'a6',0 139 | 140 | ; -------------------------------------------------------------- 141 | 142 | #ifdef ASM68K 143 | include "..\..\..\build\modules\errorhandler\asm68k-debug\ErrorHandler.asm" 144 | #else 145 | include "..\..\..\build\modules\errorhandler\as\ErrorHandler.asm" 146 | #endif 147 | 148 | -------------------------------------------------------------------------------- /modules/errorhandler/tests/cbundle_disclaimer: -------------------------------------------------------------------------------- 1 | 2 | ; ############################################################### 3 | ; # WARNING # 4 | ; ############################################################### 5 | ; # This file was auto-generated by CBundle untility! # 6 | ; # DO NOT make changes to this file directly, as they will be # 7 | ; # overwritten upon next compilation! # 8 | ; # Please refer to the main file used for code generation # 9 | ; ############################################################### 10 | -------------------------------------------------------------------------------- /modules/errorhandler/tests/console-run.asm: -------------------------------------------------------------------------------- 1 | 2 | ; =============================================================== 3 | ; --------------------------------------------------------------- 4 | ; Error handling and debugging modules 5 | ; 2016-2017, Vladikcomper 6 | ; --------------------------------------------------------------- 7 | ; Console testing module 8 | ; --------------------------------------------------------------- 9 | 10 | include "..\..\..\build\modules\mdshell\headless\MDShell.asm" 11 | 12 | include "..\..\..\build\modules\errorhandler\asm68k-debug\Debugger.asm" 13 | 14 | ; -------------------------------------------------------------- 15 | 16 | Main: 17 | Console.Run TestProgram 18 | 19 | ; -------------------------------------------------------------- 20 | TestProgram: 21 | Console.SetXY #3, #2 22 | 23 | moveq #8, d0 24 | 25 | @loop: Console.Write "%%Iteration %#%<.w d0>%" 26 | dbf d0, @loop 27 | 28 | Console.Write "%Label fetch: %%%<.l #GlobalData+$10 sym>" 29 | moveq #2, d0 30 | Console.SetXY #1, <@YPositionRef(pc,d0)> 31 | Console.WriteLine "SetXY test!" 32 | rts 33 | 34 | @YPositionRef: 35 | dc.w 0, 25 36 | 37 | ; -------------------------------------------------------------- 38 | GlobalData: 39 | dc.w @record2 40 | 41 | @record0: 42 | dc.w 0 43 | @record1: 44 | dc.w 1 45 | @record2: 46 | dc.w 2 47 | 48 | dc.b 'abcdefghi',0 49 | dc.b 'testing out ...',0 50 | even 51 | 52 | 53 | ; -------------------------------------------------------------- 54 | 55 | include "..\..\..\build\modules\errorhandler\asm68k-debug\ErrorHandler.asm" 56 | -------------------------------------------------------------------------------- /modules/errorhandler/tests/console-utils.asm: -------------------------------------------------------------------------------- 1 | #include cbundle_disclaimer 2 | 3 | ; =============================================================== 4 | ; --------------------------------------------------------------- 5 | ; Error handling and debugging modules 6 | ; 2016-2017, Vladikcomper 7 | ; --------------------------------------------------------------- 8 | ; Console run and console clear tests 9 | ; --------------------------------------------------------------- 10 | 11 | __DEBUG__: equ 1 ; enable KDebug 12 | 13 | include "..\..\..\build\modules\mdshell\headless\MDShell.asm" 14 | 15 | #ifdef ASM68K 16 | opt l+ ; use "." for local labels for AS compatibility 17 | 18 | include "..\..\..\build\modules\errorhandler\asm68k-debug\Debugger.asm" 19 | #else 20 | include "..\..\..\build\modules\errorhandler\as\Debugger.asm" 21 | #endif 22 | 23 | ; -------------------------------------------------------------- 24 | 25 | Main: 26 | ; Initialize registers with pseudo-random values, 27 | ; found at "RegisterData" byte-array (see below) 28 | movem.l RegisterData(pc), d0-a6 29 | 30 | Console.Run TestProgram 31 | 32 | ; -------------------------------------------------------------- 33 | TestProgram: 34 | KDebug.WriteLine "Entering test program..." 35 | 36 | Console.SetXY #1, #1 37 | Console.Write "%Refreshing in ~1 second..." 38 | KDebug.WriteLine "About to call Console.Sleep..." 39 | Console.Sleep #60 40 | KDebug.WriteLine "Console.Sleep finished" 41 | Console.Clear 42 | 43 | Console.WriteLine "Refreshed!" 44 | Console.Sleep #0 45 | Console.WriteLine "Printed a line without a delay!" 46 | 47 | Console.Sleep #30 48 | Console.Write "Minimal sleep... #1" 49 | Console.Sleep #30 50 | Console.Write "Minimal sleep... #2" 51 | Console.Sleep #30 52 | Console.WriteLine "Minimal sleep... #3" 53 | Console.Sleep #30 54 | 55 | jsr CheckRegisterIntergity(pc) 56 | 57 | Console.Write "Paused. Press A/B/C/Start to continue..." 58 | KDebug.WriteLine "Prepare to call Console.Pause..." 59 | Console.Pause 60 | KDebug.WriteLine "Console.Pause called" 61 | Console.WriteLine "WELL PRESSED!" 62 | KDebug.WriteLine "Printed success message to the console" 63 | 64 | jsr CheckRegisterIntergity(pc) 65 | 66 | KDebug.WriteLine "Testing KDebug writes exclusively..." 67 | KDebug.Write "You should see " 68 | KDebug.Write "this line once endl token is encountered!%" 69 | KDebug.Write "This line is extremely long and certainly flushes the buffer several times!" 70 | KDebug.BreakLine 71 | 72 | KDebug.StartTimer 73 | nop 74 | nop 75 | KDebug.EndTimer 76 | 77 | KDebug.StartTimer 78 | nop 79 | nop 80 | KDebug.EndTimer 81 | 82 | KDebug.StartTimer 83 | nop 84 | nop 85 | KDebug.EndTimer 86 | 87 | KDebug.StartTimer 88 | nop 89 | nop 90 | KDebug.EndTimer 91 | 92 | KDebug.WriteLine "You should see debugger now. Type 'c' and press Enter to continue..." 93 | KDebug.BreakPoint 94 | 95 | Console.BreakLine 96 | Console.WriteLine "ALL DONE!" 97 | ; fallthrough 98 | 99 | ; -------------------------------------------------------------- 100 | ; Subroutine that check if current register values match 101 | ; array they were initialized with ... 102 | ; -------------------------------------------------------------- 103 | 104 | CheckRegisterIntergity: 105 | movem.l d0-a6, -(sp) 106 | 107 | lea (sp), a0 ; a0 = registers dump pointer 108 | lea RegisterData(pc), a1 ; a1 = source registers pointer 109 | moveq #15-1, d0 ; d0 = number of registers minus 1 110 | 111 | .loop: 112 | cmpm.l (a0)+, (a1)+ 113 | dbne d0, .loop 114 | bne.s .corrupted 115 | movem.l (sp)+, d0-a6 116 | rts 117 | 118 | ; -------------------------------------------------------------- 119 | .corrupted: 120 | subq.w #4, a0 121 | subq.w #4, a1 122 | lea RegisterNames-RegisterData(a1), a2 123 | lea $3C(sp), a3 124 | 125 | #ifdef ASM68K 126 | Console.Write "%@%<.l (a3) sym|split>: % Register %%<.l a2 str>% corrupted!% Got %%<.l (a0)>%, expected %%<.l (a1)>%" 127 | #else 128 | ## ; AS assembler implementation has limitations on formatting capabilities 129 | ## ; Moreover, AS is incapable of parsing stings that include more than 160 characters 130 | Console.Write "%%@%<.l (a3) sym|split>: %% Register %%<.l a2 str>% corrupted!" 131 | Console.Write "% Got %%<.l (a0)>%, expected %%<.l (a1)>%%" 132 | #endif 133 | bra * 134 | 135 | ; -------------------------------------------------------------- 136 | RegisterData: 137 | dc.b $47, $2F, $74, $1E, $5B, $57, $06, $22, $38, $3D, $52, $9E, $AF, $4F 138 | dc.b $BD, $96, $F0, $8A, $3B, $BB, $B9, $E2, $96, $B0, $6E, $26, $FB, $C1 139 | dc.b $6D, $21, $D0, $06, $41, $04, $0E, $FD, $93, $72, $92, $32, $E0, $AB 140 | dc.b $2F, $77, $70, $A8, $76, $B6, $0F, $3C, $6D, $7C, $70, $72, $B5, $AA 141 | dc.b $5A, $CB, $DC, $F9 142 | 143 | RegisterNames: 144 | dc.w 'd0',0,'d1',0,'d2',0,'d3',0,'d4',0,'d5',0,'d6',0,'d7',0 145 | dc.w 'a0',0,'a1',0,'a2',0,'a3',0,'a4',0,'a5',0,'a6',0 146 | 147 | ; -------------------------------------------------------------- 148 | 149 | #ifdef ASM68K 150 | include "..\..\..\build\modules\errorhandler\asm68k-debug\ErrorHandler.asm" 151 | #else 152 | include "..\..\..\build\modules\errorhandler\as\ErrorHandler.asm" 153 | #endif 154 | 155 | -------------------------------------------------------------------------------- /modules/errorhandler/tests/linkable.asm: -------------------------------------------------------------------------------- 1 | 2 | ; =============================================================== 3 | ; --------------------------------------------------------------- 4 | ; Error handling and debugging modules 5 | ; 6 | ; (c) 2016-2024, Vladikcomper 7 | ; --------------------------------------------------------------- 8 | ; Linkable bundle testing module 9 | ; --------------------------------------------------------------- 10 | 11 | section rom 12 | 13 | xdef SymbolData_Ptr 14 | 15 | ; -------------------------------------------------------------- 16 | 17 | include "..\..\..\build\modules\mdshell\headless\MDShell.asm" 18 | 19 | include "..\..\..\build\modules\errorhandler\asm68k-linkable\Debugger.asm" 20 | 21 | ; -------------------------------------------------------------- 22 | 23 | SymbolData_Ptr: equ $200 24 | 25 | ; -------------------------------------------------------------- 26 | 27 | Main: 28 | KDebug.WriteLine "Entering Main..." 29 | 30 | moveq #$5F, d7 31 | RaiseError 'Oh look! d7 = %<.w d7>', MyErrorHandler 32 | 33 | ; -------------------------------------------------------------- 34 | MyErrorHandler: 35 | Console.WriteLine 'Hello, this is a dummy debugger~' 36 | 37 | Console.WriteLine '%d7 is %%<.w d7>% in hex' 38 | Console.WriteLine '- which is %%<.w d7 dec>% in dec' 39 | Console.WriteLine '- which is %%<.w d7 bin>% in binary' 40 | 41 | Console.WriteLine '%Press any key to continue...' 42 | Console.Pause 43 | 44 | Console.WriteLine 'Thank you for pressing!' 45 | rts 46 | -------------------------------------------------------------------------------- /modules/errorhandler/tests/raise-error.asm: -------------------------------------------------------------------------------- 1 | 2 | ; =============================================================== 3 | ; --------------------------------------------------------------- 4 | ; Error handling and debugging modules 5 | ; 6 | ; (c) 2016-2024, Vladikcomper 7 | ; --------------------------------------------------------------- 8 | ; Console testing module 9 | ; --------------------------------------------------------------- 10 | 11 | include "..\..\..\build\modules\mdshell\headless\MDShell.asm" 12 | 13 | include "..\..\..\build\modules\errorhandler\asm68k-debug\Debugger.asm" 14 | 15 | ; -------------------------------------------------------------- 16 | 17 | Main: 18 | moveq #$5F, d7 19 | 20 | RaiseError 'Oh look! d7 = %<.w d7>', MyErrorHandler 21 | 22 | ; -------------------------------------------------------------- 23 | MyErrorHandler: 24 | Console.WriteLine 'Hello, this is a dummy debugger~' 25 | 26 | Console.WriteLine '%d7 is %%<.w d7>% in hex' 27 | Console.WriteLine '- which is %%<.w d7 dec>% in dec' 28 | Console.WriteLine '- which is %%<.w d7 bin>% in binary' 29 | 30 | rts 31 | 32 | ; -------------------------------------------------------------- 33 | 34 | include "..\..\..\build\modules\errorhandler\asm68k-debug\ErrorHandler.asm" 35 | -------------------------------------------------------------------------------- /modules/exec/as/as.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/exec/as/as.msg -------------------------------------------------------------------------------- /modules/exec/as/asw.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/exec/as/asw.exe -------------------------------------------------------------------------------- /modules/exec/as/cmdarg.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/exec/as/cmdarg.msg -------------------------------------------------------------------------------- /modules/exec/as/ioerrs.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/exec/as/ioerrs.msg -------------------------------------------------------------------------------- /modules/exec/as/p2bin.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/exec/as/p2bin.exe -------------------------------------------------------------------------------- /modules/exec/as/p2bin.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/exec/as/p2bin.msg -------------------------------------------------------------------------------- /modules/exec/as/tools.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/exec/as/tools.msg -------------------------------------------------------------------------------- /modules/exec/asm68k.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/exec/asm68k.exe -------------------------------------------------------------------------------- /modules/exec/psylink.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/exec/psylink.exe -------------------------------------------------------------------------------- /modules/mdshell-core/Makefile: -------------------------------------------------------------------------------- 1 | 2 | include ../../utils/.Makefile # For $(CONVSYM), $(CBUNDLE) etc 3 | 4 | 5 | BUILD_DIR ?= ../../build/modules/mdshell-core 6 | SRC_DIR ?= . 7 | 8 | SRC_FILES ?= $(wildcard $(SRC_DIR)/*.asm) $(wildcard ../errorhandler-core/*.asm) $(wildcard ../core/*.asm) 9 | 10 | ASM68K ?= wine ../exec/asm68k.exe 11 | ASFLAGS := /q /k /m /o c+,ws+,op+,os+,ow+,oz+,oaq+,osq+,omq+,ae- 12 | 13 | .PHONY: all release release-linkable headless clean 14 | 15 | all: release release-linkable headless 16 | 17 | release: $(BUILD_DIR)/MDShell.Blob.asm $(BUILD_DIR)/MDShell.Globals.asm 18 | 19 | release-linkable: $(BUILD_DIR)/MDShell.obj $(BUILD_DIR)/MDShell.Refs.asm 20 | 21 | headless: $(BUILD_DIR)/MDShell.Headless.Blob.asm 22 | 23 | clean: 24 | rm -f $(BUILD_DIR)/*.* 25 | 26 | 27 | # Raw unlinked binaries 28 | 29 | $(BUILD_DIR)/MDShell.bin $(BUILD_DIR)/MDShell.sym &: $(SRC_FILES) | $(BUILD_DIR) 30 | $(ASM68K) $(ASFLAGS) /p $(SRC_DIR)/MDShell.asm, $(BUILD_DIR)/MDShell.bin, $(BUILD_DIR)/MDShell.sym, $(BUILD_DIR)/MDShell.lst 31 | 32 | $(BUILD_DIR)/MDShell.Headless.bin $(BUILD_DIR)/MDShell.Headless.sym &: $(SRC_FILES) | $(BUILD_DIR) 33 | $(ASM68K) $(ASFLAGS) /p /e __HEADLESS__ $(SRC_DIR)/MDShell.asm, $(BUILD_DIR)/MDShell.Headless.bin, $(BUILD_DIR)/MDShell.Headless.sym, $(BUILD_DIR)/MDShell.Headless.lst 34 | 35 | 36 | # ASM68K-linkable object file 37 | 38 | $(BUILD_DIR)/MDShell.obj $(BUILD_DIR)/MDShell.Linkable.sym &: $(SRC_FILES) | $(BUILD_DIR) 39 | $(ASM68K) $(ASFLAGS) /l /e __LINKABLE__ $(SRC_DIR)/MDShell.asm, $(BUILD_DIR)/MDShell.obj, $(BUILD_DIR)/MDShell.Linkable.sym, $(BUILD_DIR)/MDShell.Linkable.lst 40 | 41 | # Global symbol tables 42 | 43 | $(BUILD_DIR)/MDShell.Globals.asm: $(BUILD_DIR)/MDShell.sym | $(BUILD_DIR) $(CONVSYM) 44 | $(CONVSYM) $^ $@ -output asm -inopt "/processLocals-" -filter "MDDBG__.+" 45 | 46 | $(BUILD_DIR)/MDShell.Refs.asm: $(BUILD_DIR)/MDShell.Linkable.sym | $(BUILD_DIR) $(CONVSYM) 47 | $(CONVSYM) $^ $@ -output asm -outopt " xref %s" -inopt "/processLocals-" -filter "MDDBG_.+" 48 | 49 | 50 | # Linkable blobs in assembly format 51 | 52 | $(BUILD_DIR)/MDShell.Blob.asm: $(BUILD_DIR)/MDShell.bin $(BUILD_DIR)/MDShell.SymbolTable.log $(SRC_DIR)/inject-tables/MDShell.txt | $(BUILD_DIR) 53 | $(BLOBTOASM) $(BUILD_DIR)/MDShell.bin $@ -t $(BUILD_DIR)/MDShell.SymbolTable.log -m $(SRC_DIR)/inject-tables/MDShell.txt 54 | 55 | $(BUILD_DIR)/MDShell.Headless.Blob.asm: $(BUILD_DIR)/MDShell.Headless.bin $(BUILD_DIR)/MDShell.Headless.SymbolTable.log $(SRC_DIR)/inject-tables/MDShell.txt | $(BUILD_DIR) 56 | $(BLOBTOASM) $(BUILD_DIR)/MDShell.Headless.bin $@ -t $(BUILD_DIR)/MDShell.Headless.SymbolTable.log -m $(SRC_DIR)/inject-tables/MDShell.Headless.txt 57 | 58 | 59 | 60 | $(BUILD_DIR)/MDShell.SymbolTable.log: $(BUILD_DIR)/MDShell.sym | $(BUILD_DIR) $(CONVSYM) 61 | $(CONVSYM) $^ $@ -output asm -outopt "%s: %X" 62 | 63 | $(BUILD_DIR)/MDShell.Headless.SymbolTable.log: $(BUILD_DIR)/MDShell.Headless.sym | $(BUILD_DIR) $(CONVSYM) 64 | $(CONVSYM) $^ $@ -output asm -outopt "%s: %X" 65 | 66 | 67 | $(BUILD_DIR): 68 | mkdir -p $(BUILD_DIR) 69 | -------------------------------------------------------------------------------- /modules/mdshell-core/Makefile.win: -------------------------------------------------------------------------------- 1 | 2 | include ..\..\utils\.Makefile.win # For $(CONVSYM), $(CBUNDLE) etc 3 | 4 | 5 | BUILD_DIR ?= ..\..\build\modules\mdshell-core 6 | SRC_DIR ?= . 7 | 8 | SRC_FILES ?= $(wildcard $(SRC_DIR)\*.asm) $(wildcard ..\errorhandler-core\*.asm) $(wildcard ..\core\*.asm) 9 | 10 | ASM68K ?= ..\exec\asm68k.exe 11 | ASFLAGS := /q /k /m /o c+,ws+,op+,os+,ow+,oz+,oaq+,osq+,omq+,ae- 12 | 13 | .PHONY: all release release-linkable headless clean 14 | 15 | all: release release-linkable headless 16 | 17 | release: $(BUILD_DIR)\MDShell.Blob.asm $(BUILD_DIR)\MDShell.Globals.asm 18 | 19 | release-linkable: $(BUILD_DIR)\MDShell.obj $(BUILD_DIR)\MDShell.Refs.asm 20 | 21 | headless: $(BUILD_DIR)\MDShell.Headless.Blob.asm 22 | 23 | clean: 24 | del /q /f $(BUILD_DIR)\*.* 25 | 26 | 27 | # Raw unlinked binaries 28 | 29 | $(BUILD_DIR)\MDShell.bin $(BUILD_DIR)\MDShell.sym &: $(SRC_FILES) | $(BUILD_DIR) 30 | $(ASM68K) $(ASFLAGS) /p $(SRC_DIR)\MDShell.asm, $(BUILD_DIR)\MDShell.bin, $(BUILD_DIR)\MDShell.sym, $(BUILD_DIR)\MDShell.lst 31 | 32 | $(BUILD_DIR)\MDShell.Headless.bin $(BUILD_DIR)\MDShell.Headless.sym &: $(SRC_FILES) | $(BUILD_DIR) 33 | $(ASM68K) $(ASFLAGS) /p /e _HEADLESS_=1 $(SRC_DIR)\MDShell.asm, $(BUILD_DIR)\MDShell.Headless.bin, $(BUILD_DIR)\MDShell.Headless.sym, $(BUILD_DIR)\MDShell.Headless.lst 34 | 35 | 36 | # ASM68K-linkable object file 37 | 38 | $(BUILD_DIR)\MDShell.obj $(BUILD_DIR)\MDShell.Linkable.sym &: $(SRC_FILES) | $(BUILD_DIR) 39 | $(ASM68K) $(ASFLAGS) /l /e __LINKABLE__ $(SRC_DIR)\MDShell.asm, $(BUILD_DIR)\MDShell.obj, $(BUILD_DIR)/MDShell.Linkable.sym, $(BUILD_DIR)\MDShell.Linkable.lst 40 | 41 | # Global symbol tables 42 | 43 | $(BUILD_DIR)\MDShell.Globals.asm: $(BUILD_DIR)\MDShell.sym | $(BUILD_DIR) $(CONVSYM) 44 | $(CONVSYM) $^ $@ -output asm -filter "MDDBG_.+" 45 | 46 | $(BUILD_DIR)\MDShell.Refs.asm: $(BUILD_DIR)\MDShell.Linkable.sym | $(BUILD_DIR) $(CONVSYM) 47 | $(CONVSYM) $^ $@ -output asm -outopt " xref %s" -inopt "/processLocals-" -filter "MDDBG_.+" 48 | 49 | 50 | # Linkable blobs in assembly format 51 | 52 | $(BUILD_DIR)\MDShell.Blob.asm: $(BUILD_DIR)\MDShell.bin $(BUILD_DIR)\MDShell.SymbolTable.log $(SRC_DIR)\inject-tables\MDShell.txt | $(BUILD_DIR) 53 | $(BLOBTOASM) $(BUILD_DIR)\MDShell.bin $@ -t $(BUILD_DIR)\MDShell.SymbolTable.log -m $(SRC_DIR)\inject-tables\MDShell.txt 54 | 55 | $(BUILD_DIR)\MDShell.Headless.Blob.asm: $(BUILD_DIR)\MDShell.Headless.bin $(BUILD_DIR)\MDShell.Headless.SymbolTable.log $(SRC_DIR)\inject-tables\MDShell.txt | $(BUILD_DIR) 56 | $(BLOBTOASM) $(BUILD_DIR)\MDShell.Headless.bin $@ -t $(BUILD_DIR)\MDShell.Headless.SymbolTable.log -m $(SRC_DIR)\inject-tables\MDShell.Headless.txt 57 | 58 | 59 | 60 | $(BUILD_DIR)\MDShell.SymbolTable.log: $(BUILD_DIR)\MDShell.sym | $(BUILD_DIR) $(CONVSYM) 61 | $(CONVSYM) $^ $@ -output asm -outopt "%s: %X" 62 | 63 | $(BUILD_DIR)\MDShell.Headless.SymbolTable.log: $(BUILD_DIR)\MDShell.Headless.sym | $(BUILD_DIR) $(CONVSYM) 64 | $(CONVSYM) $^ $@ -output asm -outopt "%s: %X" 65 | 66 | 67 | $(BUILD_DIR): 68 | -md $(BUILD_DIR) 69 | -------------------------------------------------------------------------------- /modules/mdshell-core/inject-tables/MDShell.Headless.txt: -------------------------------------------------------------------------------- 1 | __inject_main+2:long:inline: Main 2 | -------------------------------------------------------------------------------- /modules/mdshell-core/inject-tables/MDShell.txt: -------------------------------------------------------------------------------- 1 | Vectors+8:long:inline: BusError 2 | Vectors+C:long:inline: AddressError 3 | Vectors+10:long:inline: IllegalInstr 4 | Vectors+14:long:inline: ZeroDivide 5 | Vectors+18:long:inline: ChkInstr 6 | Vectors+1C:long:inline: TrapvInstr 7 | Vectors+20:long:inline: PrivilegeViol 8 | Vectors+24:long:inline: Trace 9 | Vectors+28:long:inline: Line1010Emu 10 | Vectors+2C:long:inline: Line1111Emu 11 | Vectors+30:long:inline: ErrorExcept 12 | Vectors+34:long:inline: ErrorExcept 13 | Vectors+38:long:inline: ErrorExcept 14 | Vectors+3C:long:inline: ErrorExcept 15 | Vectors+40:long:inline: ErrorExcept 16 | Vectors+44:long:inline: ErrorExcept 17 | Vectors+48:long:inline: ErrorExcept 18 | Vectors+4C:long:inline: ErrorExcept 19 | Vectors+50:long:inline: ErrorExcept 20 | Vectors+54:long:inline: ErrorExcept 21 | Vectors+58:long:inline: ErrorExcept 22 | Vectors+5C:long:inline: ErrorExcept 23 | Vectors+60:long:inline: ErrorExcept 24 | Vectors+70:long:inline: MDSHELL__HBLANK_HANDLER 25 | Vectors+78:long:inline: MDSHELL__VBLANK_HANDLER 26 | __inject_main+2:long:inline: Main 27 | -------------------------------------------------------------------------------- /modules/mdshell/BUILD.md: -------------------------------------------------------------------------------- 1 | 2 | # Building MD-Shell 3 | 4 | Make sure you're in the right directory: 5 | 6 | ```sh 7 | cd modules/mdshell/ 8 | ``` 9 | 10 | Use the appropriate GNU Make invocation depending on your system: 11 | 12 | - **Linux/MacOS**: `make` 13 | - **FreeBSD**: `gmake` 14 | - **Windows**: `make -f Makefile.win` 15 | 16 | Bundles will be generated in `build/modules/mdshell`. 17 | 18 | ## Testing 19 | 20 | Various tests are also provided in form of compilable Mega-Drive ROMs to make sure MD-Shell is stable and usable. 21 | 22 | To build tests, use the appropriate GNU Make invocation depending on your system: 23 | 24 | - **Linux/MacOS**: `make tests` 25 | - **FreeBSD**: `gmake tests` 26 | - **Windows**: `make -f Makefile.win tests` 27 | 28 | ROMs will be generated in `build/modules/mdshell/tests`. Note that **this won't actually test anything yet**! You need to manually run ROMs in your favorite Sega Mega-Drive emulator to see the results. 29 | -------------------------------------------------------------------------------- /modules/mdshell/MDShell.Headless.asm: -------------------------------------------------------------------------------- 1 | ; =============================================================== 2 | ; --------------------------------------------------------------- 3 | ; MD Shell v.2.6 4 | ; 5 | ; (c) 2023-2024, Vladikcomper 6 | ; --------------------------------------------------------------- 7 | 8 | MDShell: 9 | #include ../../build/modules/mdshell-core/MDShell.Headless.Blob.asm 10 | -------------------------------------------------------------------------------- /modules/mdshell/MDShell.asm: -------------------------------------------------------------------------------- 1 | ; =============================================================== 2 | ; --------------------------------------------------------------- 3 | ; MD Shell v.2.6 4 | ; 5 | ; (c) 2023-2024, Vladikcomper 6 | ; --------------------------------------------------------------- 7 | 8 | 9 | #include ../errorhandler/Debugger.Config.asm 10 | 11 | 12 | #include ../errorhandler/Debugger.Constants.asm 13 | 14 | #ifdef LINKABLE 15 | #ifdef BUNDLE-ASM68K 16 | ; =============================================================== 17 | ; --------------------------------------------------------------- 18 | ; Symbols imported from the object file 19 | ; --------------------------------------------------------------- 20 | 21 | #include ../../build/modules/mdshell-core/MDShell.Refs.asm 22 | #else 23 | ## AS bundle doesn't support linkable builds! 24 | #endif 25 | #endif 26 | 27 | 28 | ; --------------------------------------------------------------- 29 | ; Macros 30 | ; --------------------------------------------------------------- 31 | 32 | #ifdef BUNDLE-ASM68K 33 | #include ../errorhandler/Debugger.Macros.ASM68K.asm 34 | #endif 35 | ## 36 | ## 37 | #ifdef BUNDLE-AS 38 | #include ../errorhandler/Debugger.Macros.AS.asm 39 | #endif 40 | ## 41 | 42 | 43 | #ifndef LINKABLE 44 | ; --------------------------------------------------------------- 45 | ; MD-Shell blob 46 | ; --------------------------------------------------------------- 47 | 48 | MDShell: 49 | #include ../../build/modules/mdshell-core/MDShell.Blob.asm 50 | 51 | 52 | ; --------------------------------------------------------------- 53 | ; MD-Shell's exported symbols 54 | ; --------------------------------------------------------------- 55 | 56 | #include ../../build/modules/mdshell-core/MDShell.Globals.asm 57 | 58 | #include ../errorhandler/ErrorHandler.Exceptions.asm 59 | #endif 60 | -------------------------------------------------------------------------------- /modules/mdshell/Makefile: -------------------------------------------------------------------------------- 1 | 2 | include ../../utils/.Makefile # For $(CONVSYM), $(CBUNDLE) etc 3 | 4 | 5 | BUILD_DIR ?= ../../build/modules/mdshell 6 | CORE_BUILD_DIR ?= ../../build/modules/mdshell-core 7 | SRC_DIR ?= . 8 | 9 | SRC_FILES = $(wildcard $(SRC_DIR)/*.asm) 10 | CORE_SRC_FILES ?= $(wildcard ../mdshell-core/*.asm) $(wildcard ../core/*.asm) 11 | EH_SRC_FILES = $(wildcard ../errorhandler/*.asm) 12 | EH_CORE_SRC_FILES = $(wildcard ../errorhandler-core/*.asm) $(wildcard ../core/*.asm) 13 | EXT_SRC_FILES ?= $(wildcard ../debuggers/*.asm) $(wildcard ../core/*.asm) 14 | 15 | 16 | .PHONY: all asm68k asm68k-linkable as headless tests clean 17 | 18 | all: asm68k asm68k-linkable as headless 19 | 20 | asm68k: $(BUILD_DIR)/asm68k/MDShell.asm 21 | 22 | asm68k-linkable: $(BUILD_DIR)/asm68k-linkable/MDShell.obj $(BUILD_DIR)/asm68k-linkable/MDShell.asm 23 | 24 | as: $(BUILD_DIR)/as/MDShell.asm 25 | 26 | headless: $(BUILD_DIR)/headless/MDShell.asm 27 | 28 | tests: all 29 | $(MAKE) -C tests -f .Makefile 30 | 31 | clean: 32 | rm -rf $(BUILD_DIR)/* 33 | 34 | 35 | $(BUILD_DIR)/asm68k/MDShell.asm: $(SRC_FILES) $(EH_SRC_FILES) $(CORE_BUILD_DIR)/MDShell.Globals.asm $(CORE_BUILD_DIR)/MDShell.Blob.asm | $(BUILD_DIR) $(CBUNDLE) 36 | mkdir -p $(BUILD_DIR)/asm68k 37 | $(CBUNDLE) $(SRC_DIR)/MDShell.asm -def MD-SHELL -def BUNDLE-ASM68K -def ASM68K-DOT-COMPAT -out $@ 38 | 39 | $(BUILD_DIR)/asm68k-linkable/MDShell.asm $(BUILD_DIR)/asm68k-linkable/MDShell.obj &: $(SRC_FILES) $(EH_SRC_FILES) $(CORE_BUILD_DIR)/MDShell.Refs.asm $(CORE_BUILD_DIR)/MDShell.obj | $(BUILD_DIR) $(CBUNDLE) 40 | mkdir -p $(BUILD_DIR)/asm68k-linkable 41 | $(CBUNDLE) $(SRC_DIR)/MDShell.asm -def MD-SHELL -def BUNDLE-ASM68K -def ASM68K-DOT-COMPAT -def LINKABLE -def LINKABLE-WITH-DATA-SECTION -out $(BUILD_DIR)/asm68k-linkable/MDShell.asm 42 | cp $(CORE_BUILD_DIR)/MDShell.obj $(BUILD_DIR)/asm68k-linkable/MDShell.obj 43 | 44 | $(BUILD_DIR)/as/MDShell.asm: $(SRC_FILES) $(EH_SRC_FILES) $(CORE_BUILD_DIR)/MDShell.Globals.asm $(CORE_BUILD_DIR)/MDShell.Blob.asm | $(BUILD_DIR) $(CBUNDLE) 45 | mkdir -p $(BUILD_DIR)/as 46 | $(CBUNDLE) $(SRC_DIR)/MDShell.asm -def MD-SHELL -def BUNDLE-AS -out $@ 47 | 48 | $(BUILD_DIR)/headless/MDShell.asm: $(SRC_FILES) $(EH_SRC_FILES) $(CORE_BUILD_DIR)/MDShell.Headless.Blob.asm | $(BUILD_DIR) $(CBUNDLE) 49 | mkdir -p $(BUILD_DIR)/headless 50 | $(CBUNDLE) $(SRC_DIR)/MDShell.Headless.asm -def MD-SHELL -def HEADLESS -out $@ 51 | 52 | 53 | $(CORE_BUILD_DIR)/MDShell.Refs.asm $(CORE_BUILD_DIR)/MDShell.obj &: $(CORE_SRC_FILES) $(EH_CORE_SRC_FILES) 54 | $(MAKE) -C ../mdshell-core release-linkable 55 | 56 | $(CORE_BUILD_DIR)/MDShell.Globals.asm $(CORE_BUILD_DIR)/MDShell.Blob.asm &: $(CORE_SRC_FILES) $(EH_CORE_SRC_FILES) 57 | $(MAKE) -C ../mdshell-core release 58 | 59 | $(CORE_BUILD_DIR)/MDShell.Headless.Blob.asm &: $(CORE_SRC_FILES) $(EH_CORE_SRC_FILES) 60 | $(MAKE) -C ../mdshell-core headless 61 | 62 | 63 | $(BUILD_DIR): 64 | mkdir -p $(BUILD_DIR) 65 | -------------------------------------------------------------------------------- /modules/mdshell/Makefile.win: -------------------------------------------------------------------------------- 1 | 2 | include ..\..\utils\.Makefile.win # For $(CONVSYM), $(CBUNDLE) etc 3 | 4 | 5 | BUILD_DIR ?= ..\..\build\modules\mdshell 6 | CORE_BUILD_DIR ?= ..\..\build\modules\mdshell-core 7 | SRC_DIR ?= . 8 | 9 | SRC_FILES = $(wildcard $(SRC_DIR)\*.asm) 10 | CORE_SRC_FILES ?= $(wildcard ..\mdshell-core\*.asm) $(wildcard ..\core\*.asm) 11 | EH_SRC_FILES = $(wildcard ..\errorhandler\*.asm) 12 | EH_CORE_SRC_FILES = $(wildcard ..\errorhandler-core\*.asm) $(wildcard ..\core\*.asm) 13 | EXT_SRC_FILES ?= $(wildcard ..\debuggers\*.asm) $(wildcard ..\core\*.asm) 14 | 15 | 16 | .PHONY: all asm68k asm68k-linkable as headless tests clean 17 | 18 | all: asm68k asm68k-linkable as headless 19 | 20 | asm68k: $(BUILD_DIR)\asm68k\MDShell.asm 21 | 22 | asm68k-linkable: $(BUILD_DIR)\asm68k-linkable\MDShell.obj $(BUILD_DIR)\asm68k-linkable\MDShell.asm 23 | 24 | as: $(BUILD_DIR)\as\MDShell.asm 25 | 26 | headless: $(BUILD_DIR)\headless\MDShell.asm 27 | 28 | tests: asm68k as headless 29 | make -C tests -f .Makefile.win 30 | 31 | clean: 32 | rd /s /q $(BUILD_DIR) 33 | 34 | 35 | $(BUILD_DIR)\asm68k\MDShell.asm: $(SRC_FILES) $(EH_SRC_FILES) $(CORE_BUILD_DIR)\MDShell.Globals.asm $(CORE_BUILD_DIR)\MDShell.Blob.asm | $(BUILD_DIR) $(CBUNDLE) 36 | -md $(BUILD_DIR)\asm68k 37 | $(CBUNDLE) $(SRC_DIR)\MDShell.asm -def MD-SHELL -def BUNDLE-ASM68K -def ASM68K-DOT-COMPAT -out $@ 38 | 39 | $(BUILD_DIR)\asm68k-linkable\MDShell.asm $(BUILD_DIR)\asm68k-linkable\MDShell.obj &: $(SRC_FILES) $(EH_SRC_FILES) $(CORE_BUILD_DIR)\MDShell.Refs.asm $(CORE_BUILD_DIR)\MDShell.obj | $(BUILD_DIR) $(CBUNDLE) 40 | -md $(BUILD_DIR)\asm68k-linkable 41 | $(CBUNDLE) $(SRC_DIR)\MDShell.asm -def MD-SHELL -def BUNDLE-ASM68K -def ASM68K-DOT-COMPAT -def LINKABLE -out $(BUILD_DIR)\asm68k-linkable\MDShell.asm 42 | copy $(CORE_BUILD_DIR)\MDShell.obj $(BUILD_DIR)\asm68k-linkable\MDShell.obj 43 | 44 | $(BUILD_DIR)\as\MDShell.asm: $(SRC_FILES) $(EH_SRC_FILES) $(CORE_BUILD_DIR)\MDShell.Globals.asm $(CORE_BUILD_DIR)\MDShell.Blob.asm | $(BUILD_DIR) $(CBUNDLE) 45 | -md $(BUILD_DIR)\as 46 | $(CBUNDLE) $(SRC_DIR)\MDShell.asm -def MD-SHELL -def BUNDLE-AS -out $@ 47 | 48 | $(BUILD_DIR)\headless\MDShell.asm: $(SRC_FILES) $(EH_SRC_FILES) $(CORE_BUILD_DIR)\MDShell.Headless.Blob.asm | $(BUILD_DIR) $(CBUNDLE) 49 | -md $(BUILD_DIR)\headless 50 | $(CBUNDLE) $(SRC_DIR)\MDShell.Headless.asm -def MD-SHELL -def HEADLESS -out $@ 51 | 52 | 53 | $(CORE_BUILD_DIR)\MDShell.Refs.asm $(CORE_BUILD_DIR)\MDShell.obj &: $(CORE_SRC_FILES) $(EH_CORE_SRC_FILES) 54 | $(MAKE) -C ..\mdshell-core -f Makefile.win release-linkable 55 | 56 | $(CORE_BUILD_DIR)\MDShell.Globals.asm $(CORE_BUILD_DIR)\MDShell.Blob.asm &: $(CORE_SRC_FILES) $(EH_CORE_SRC_FILES) 57 | $(MAKE) -C ..\mdshell-core -f Makefile.win release 58 | 59 | $(CORE_BUILD_DIR)\MDShell.Headless.Blob.asm &: $(CORE_SRC_FILES) $(EH_CORE_SRC_FILES) 60 | $(MAKE) -C ..\mdshell-core -f Makefile.win headless 61 | 62 | 63 | $(BUILD_DIR): 64 | -md $(BUILD_DIR) 65 | -------------------------------------------------------------------------------- /modules/mdshell/docs/.images/mdshell-hello-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/modules/mdshell/docs/.images/mdshell-hello-world.png -------------------------------------------------------------------------------- /modules/mdshell/tests/.Makefile: -------------------------------------------------------------------------------- 1 | 2 | # WARNING! It's not recommended to invoke this Makefile manually, since it doesn't track test dependencies. 3 | # Please run `make tests` from the upper directory instead. 4 | 5 | include ../../../utils/.Makefile # For $(CONVSYM), $(CBUNDLE) etc 6 | 7 | ASM68K := wine ../../exec/asm68k.exe 8 | PSYLINK := wine ../../exec/psylink.exe 9 | AS := wine ../../exec/as/asw.exe 10 | P2BIN := wine ../../exec/as/p2bin.exe 11 | 12 | TEST_BUILD_DIR := ../../../build/modules/mdshell/tests 13 | 14 | BUILD_DIR := ../../../build/modules/mdshell 15 | 16 | 17 | .PHONY: all hello-world flow-test raise-error linkable clean 18 | 19 | all: hello-world flow-test raise-error linkable 20 | 21 | clean: 22 | rm -f $(TEST_BUILD_DIR)/* 23 | 24 | 25 | hello-world: | $(TEST_BUILD_DIR) $(CONVSYM) 26 | $(ASM68K) /q /k /m /o c+,ws+,op+,os+,ow+,oz+,oaq+,osq+,omq+,ae- /p hello-world.asm, $(TEST_BUILD_DIR)/hello-world.gen, $(TEST_BUILD_DIR)/hello-world.sym, $(TEST_BUILD_DIR)/hello-world.lst 27 | $(CONVSYM) $(TEST_BUILD_DIR)/hello-world.sym $(TEST_BUILD_DIR)/hello-world.gen -a -ref 200 -debug 28 | 29 | 30 | raise-error: | $(TEST_BUILD_DIR) $(CONVSYM) 31 | $(ASM68K) /q /k /m /o c+,ws+,op+,os+,ow+,oz+,oaq+,osq+,omq+,ae- /p raise-error.asm, $(TEST_BUILD_DIR)/raise-error.gen, $(TEST_BUILD_DIR)/raise-error.sym, $(TEST_BUILD_DIR)/raise-error.lst 32 | $(CONVSYM) $(TEST_BUILD_DIR)/raise-error.sym $(TEST_BUILD_DIR)/raise-error.gen -a -ref 200 -debug 33 | 34 | 35 | linkable: | $(TEST_BUILD_DIR) $(MDSHELL_HEADLESS) $(CONVSYM) 36 | $(ASM68K) /q /k /m /o c+,ws+,op+,os+,ow+,oz+,oaq+,osq+,omq+,ae- /g /l linkable.asm, $(TEST_BUILD_DIR)/linkable.obj, , $(TEST_BUILD_DIR)/linkable.lst 37 | $(PSYLINK) /q /p $(BUILD_DIR)/asm68k-linkable/MDShell.obj $(TEST_BUILD_DIR)/linkable.obj,$(TEST_BUILD_DIR)/linkable.gen,$(TEST_BUILD_DIR)/linkable.sym 38 | $(CONVSYM) $(TEST_BUILD_DIR)/linkable.sym $(TEST_BUILD_DIR)/linkable.gen -input asm68k_sym -output deb2 -a -ref 200 39 | 40 | 41 | flow-test: | $(TEST_BUILD_DIR) $(CONVSYM) $(CBUNDLE) 42 | $(CBUNDLE) flow-test.asm -def ASM68K -out $(TEST_BUILD_DIR)/flow-test-asm68k.asm 43 | $(CBUNDLE) flow-test.asm -def AS -out $(TEST_BUILD_DIR)/flow-test-as.asm 44 | 45 | $(ASM68K) /q /k /m /o c+,ws+,op+,os+,ow+,oz+,oaq+,osq+,omq+,ae-,l+ /p $(TEST_BUILD_DIR)/flow-test-asm68k.asm, $(TEST_BUILD_DIR)/flow-test-asm68k.gen, $(TEST_BUILD_DIR)/flow-test-asm68k.sym, $(TEST_BUILD_DIR)/flow-test-asm68k.lst 46 | $(CONVSYM) $(TEST_BUILD_DIR)/flow-test-asm68k.sym $(TEST_BUILD_DIR)/flow-test-asm68k.gen -in asm68k_sym -a -ref 200 -debug 47 | 48 | set AS_MSGPATH="..\..\exec\as" 49 | set USEANSI=n 50 | $(AS) -xx -i . -A -L -OLIST $(TEST_BUILD_DIR)/flow-test-as.lst $(TEST_BUILD_DIR)/flow-test-as.asm -o $(TEST_BUILD_DIR)/flow-test-as.p 51 | $(P2BIN) $(TEST_BUILD_DIR)/flow-test-as.p $(TEST_BUILD_DIR)/flow-test-as.gen 52 | rm $(TEST_BUILD_DIR)/flow-test-as.p 53 | $(CONVSYM) $(TEST_BUILD_DIR)/flow-test-as.lst $(TEST_BUILD_DIR)/flow-test-as.gen -in as_lst -a -ref 200 54 | 55 | 56 | $(TEST_BUILD_DIR): 57 | mkdir -p $(TEST_BUILD_DIR) 58 | -------------------------------------------------------------------------------- /modules/mdshell/tests/.Makefile.win: -------------------------------------------------------------------------------- 1 | 2 | # WARNING! It's not recommended to invoke this Makefile manually, since it doesn't track test dependencies. 3 | # Please run `make tests` from the upper directory instead. 4 | 5 | include ../../../utils/.Makefile.win # For $(CONVSYM), $(CBUNDLE) etc 6 | 7 | ASM68K := ..\..\exec\asm68k.exe 8 | PSYLINK := ..\..\exec\psylink.exe 9 | AS := ..\..\exec\as\asw.exe 10 | P2BIN := ..\..\exec\as\p2bin.exe 11 | 12 | TEST_BUILD_DIR := ..\..\..\build\modules\mdshell\tests 13 | 14 | BUILD_DIR := ..\..\..\build\modules\mdshell 15 | 16 | 17 | .PHONY: all hello-world flow-test raise-error linkable clean 18 | 19 | all: hello-world flow-test raise-error linkable 20 | 21 | clean: 22 | del /q /f $(TEST_BUILD_DIR)\* 23 | 24 | 25 | hello-world: | $(TEST_BUILD_DIR) $(CONVSYM) 26 | $(ASM68K) /q /k /m /o c+,ws+,op+,os+,ow+,oz+,oaq+,osq+,omq+,ae- /p hello-world.asm, $(TEST_BUILD_DIR)\hello-world.gen, $(TEST_BUILD_DIR)\hello-world.sym, $(TEST_BUILD_DIR)\hello-world.lst 27 | $(CONVSYM) $(TEST_BUILD_DIR)\hello-world.sym $(TEST_BUILD_DIR)\hello-world.gen -a -ref 200 -debug 28 | 29 | 30 | raise-error: | $(TEST_BUILD_DIR) $(CONVSYM) 31 | $(ASM68K) /q /k /m /o c+,ws+,op+,os+,ow+,oz+,oaq+,osq+,omq+,ae- /p raise-error.asm, $(TEST_BUILD_DIR)/raise-error.gen, $(TEST_BUILD_DIR)/raise-error.sym, $(TEST_BUILD_DIR)/raise-error.lst 32 | $(CONVSYM) $(TEST_BUILD_DIR)\raise-error.sym $(TEST_BUILD_DIR)/raise-error.gen -a -ref 200 -debug 33 | 34 | 35 | linkable: | $(TEST_BUILD_DIR) $(MDSHELL_HEADLESS) $(CONVSYM) 36 | $(ASM68K) /q /k /m /o c+,ws+,op+,os+,ow+,oz+,oaq+,osq+,omq+,ae- /g /l linkable.asm, $(TEST_BUILD_DIR)\linkable.obj, , $(TEST_BUILD_DIR)\linkable.lst 37 | $(PSYLINK) /q /p $(BUILD_DIR)\asm68k-linkable/MDShell.obj $(TEST_BUILD_DIR)\linkable.obj,$(TEST_BUILD_DIR)\linkable.gen,$(TEST_BUILD_DIR)\linkable.sym 38 | $(CONVSYM) $(TEST_BUILD_DIR)\linkable.sym $(TEST_BUILD_DIR)\linkable.gen -input asm68k_sym -output deb2 -a -ref 200 39 | 40 | 41 | flow-test: | $(TEST_BUILD_DIR) $(CONVSYM) $(CBUNDLE) 42 | $(CBUNDLE) flow-test.asm -def ASM68K -out $(TEST_BUILD_DIR)\flow-test-asm68k.asm 43 | $(CBUNDLE) flow-test.asm -def AS -out $(TEST_BUILD_DIR)\flow-test-as.asm 44 | 45 | $(ASM68K) /q /k /m /o c+,ws+,op+,os+,ow+,oz+,oaq+,osq+,omq+,ae-,l+ /p $(TEST_BUILD_DIR)\flow-test-asm68k.asm, $(TEST_BUILD_DIR)\flow-test-asm68k.gen, $(TEST_BUILD_DIR)\flow-test-asm68k.sym, $(TEST_BUILD_DIR)\flow-test-asm68k.lst 46 | $(CONVSYM) $(TEST_BUILD_DIR)\flow-test-asm68k.sym $(TEST_BUILD_DIR)\flow-test-asm68k.gen -in asm68k_sym -a -ref 200 -debug 47 | 48 | set AS_MSGPATH="..\..\exec\as" 49 | set USEANSI=n 50 | $(AS) -xx -i . -A -L -OLIST $(TEST_BUILD_DIR)\flow-test-as.lst $(TEST_BUILD_DIR)\flow-test-as.asm -o $(TEST_BUILD_DIR)\flow-test-as.p 51 | $(P2BIN) $(TEST_BUILD_DIR)\flow-test-as.p $(TEST_BUILD_DIR)\flow-test-as.gen 52 | del /q /f $(TEST_BUILD_DIR)\flow-test-as.p 53 | $(CONVSYM) $(TEST_BUILD_DIR)\flow-test-as.lst $(TEST_BUILD_DIR)\flow-test-as.gen -in as_lst -a -ref 200 54 | 55 | 56 | $(TEST_BUILD_DIR): 57 | -md $(TEST_BUILD_DIR) 58 | -------------------------------------------------------------------------------- /modules/mdshell/tests/cbundle_disclaimer: -------------------------------------------------------------------------------- 1 | 2 | ; ############################################################### 3 | ; # WARNING # 4 | ; ############################################################### 5 | ; # This file was auto-generated by CBundle untility! # 6 | ; # DO NOT make changes to this file directly, as they will be # 7 | ; # overwritten upon next compilation! # 8 | ; # Please refer to the main file used for code generation # 9 | ; ############################################################### 10 | -------------------------------------------------------------------------------- /modules/mdshell/tests/hello-world.asm: -------------------------------------------------------------------------------- 1 | 2 | include "..\..\..\build\modules\mdshell\asm68k\MDShell.asm" 3 | 4 | Main: 5 | Console.WriteLine "Hello, world!" 6 | rts 7 | -------------------------------------------------------------------------------- /modules/mdshell/tests/linkable.asm: -------------------------------------------------------------------------------- 1 | 2 | ; =============================================================== 3 | ; --------------------------------------------------------------- 4 | ; MD Shell 5 | ; 6 | ; (c) 2016-2024, Vladikcomper 7 | ; --------------------------------------------------------------- 8 | ; Linkable bundle testing module 9 | ; --------------------------------------------------------------- 10 | 11 | section rom 12 | 13 | xdef Main 14 | 15 | ; -------------------------------------------------------------- 16 | 17 | include "..\..\..\build\modules\mdshell\asm68k-linkable\MDShell.asm" 18 | 19 | ; -------------------------------------------------------------- 20 | 21 | Main: 22 | ; In linkable builds, `.Write[Line]` functions store strings 23 | ; in `dbgstrings` section and they switch back and forth 24 | ; between `rom` and `dbgstrings` in macro invocation. 25 | ; In this stress test we ensure 26 | ; both ASM68K and PSYLINK are able to handle many section 27 | ; switches without issues. 28 | KDebug.WriteLine "Linkable KDebug stress test..." 29 | @counter: = 0 30 | rept 1024 31 | @counter: = @counter + 1 32 | KDebug.WriteLine "Wrote stored string \#@counter/1024." 33 | endr 34 | 35 | KDebug.WriteLine "Entering Main..." 36 | 37 | moveq #$5F, d7 38 | RaiseError 'Oh look! d7 = %<.w d7>', MyErrorHandler 39 | 40 | ; -------------------------------------------------------------- 41 | MyErrorHandler: 42 | Console.WriteLine 'Hello, this is a dummy debugger~' 43 | 44 | Console.WriteLine '%d7 is %%<.w d7>% in hex' 45 | Console.WriteLine '- which is %%<.w d7 dec>% in dec' 46 | Console.WriteLine '- which is %%<.w d7 bin>% in binary' 47 | 48 | Console.WriteLine '%Press any key to continue...' 49 | Console.Pause 50 | 51 | Console.WriteLine 'Thank you for pressing!' 52 | rts 53 | -------------------------------------------------------------------------------- /modules/mdshell/tests/raise-error.asm: -------------------------------------------------------------------------------- 1 | 2 | include "..\..\..\build\modules\mdshell\asm68k\MDShell.asm" 3 | 4 | Main: 5 | RaiseError "Intentional crash" 6 | -------------------------------------------------------------------------------- /pyrightconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "pythonVersion": "3.8", 3 | "extraPaths": ["utils/lib-py"] 4 | } -------------------------------------------------------------------------------- /utils/.Makefile: -------------------------------------------------------------------------------- 1 | 2 | # WARNING! Please don't invoke this Makefile directly 3 | 4 | UTILS_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) 5 | 6 | CONVSYM := $(UTILS_DIR)/../build/utils/convsym 7 | CBUNDLE := $(UTILS_DIR)/../build/utils/cbundle 8 | BLOBTOASM := $(UTILS_DIR)/blobtoasm/blobtoasm.py 9 | 10 | .PHONY: all 11 | 12 | all: $(CONVSYM) $(CBUNDLE) 13 | 14 | $(CONVSYM): 15 | $(MAKE) -C $(UTILS_DIR)/convsym 16 | 17 | $(CBUNDLE): 18 | $(MAKE) -C $(UTILS_DIR)/cbundle 19 | -------------------------------------------------------------------------------- /utils/.Makefile.win: -------------------------------------------------------------------------------- 1 | 2 | # WARNING! Please don't invoke this Makefile directly 3 | 4 | UTILS_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) 5 | 6 | # On Windows, executables end with .exe 7 | CONVSYM := $(UTILS_DIR)/../build/utils/convsym.exe 8 | CBUNDLE := $(UTILS_DIR)/../build/utils/cbundle.exe 9 | BLOBTOASM := python3 $(UTILS_DIR)/blobtoasm/blobtoasm.py 10 | 11 | .PHONY: all 12 | 13 | all: $(CONVSYM) $(CBUNDLE) 14 | 15 | $(CONVSYM): 16 | make -C $(UTILS_DIR)/convsym -f Makefile.win 17 | 18 | $(CBUNDLE): 19 | make -C $(UTILS_DIR)/cbundle -f Makefile.win 20 | -------------------------------------------------------------------------------- /utils/blobtoasm/readme.txt: -------------------------------------------------------------------------------- 1 | usage: blobtoasm.py [-h] [-t SYMBOLTABLE] [-m INJECTIONMAP] 2 | [-s {long,word,byte,l,w,b}] [-l UNITSPERLINE] 3 | blob outfile 4 | 5 | Renders binary files in M68K assembly. 6 | 7 | positional arguments: 8 | blob 9 | outfile 10 | 11 | options: 12 | -h, --help show this help message and exit 13 | -t SYMBOLTABLE, --symbolTable SYMBOLTABLE 14 | Path to a symbol table in log format, which may 15 | include control symbols for the conversion, e.g. 16 | __blob_start, __blob_end 17 | -m INJECTIONMAP, --injectionMap INJECTIONMAP 18 | Path to injection map, which tells how to inject 19 | symbols marked as __inject_* in symbol table 20 | -s {long,word,byte,l,w,b}, --unitSize {long,word,byte,l,w,b} 21 | Specifies the default unit size when rendering blob; 22 | this will select between dc.l, dc.w and dc.b 23 | -l UNITSPERLINE, --unitsPerLine UNITSPERLINE 24 | Specifies number of units per line 25 | -------------------------------------------------------------------------------- /utils/cbundle/Makefile: -------------------------------------------------------------------------------- 1 | 2 | BUILD_DIR ?= ../../build/utils 3 | SRC_DIR ?= src 4 | LIB_DIR ?= ../lib 5 | 6 | EXEC_NAME = cbundle 7 | EXECUTABLE = $(BUILD_DIR)/$(EXEC_NAME) 8 | 9 | CXXFLAGS ?= -std=c++20 -O2 -pedantic-errors -Wall -I../lib 10 | LDFLAGS ?= -s 11 | 12 | .PHONY: all cbundle test clean 13 | 14 | all: cbundle readme.txt 15 | 16 | cbundle: $(EXECUTABLE) 17 | 18 | test: $(EXECUTABLE) 19 | cd tests && ./test.py 20 | 21 | clean: 22 | rm -f $(EXECUTABLE) 23 | 24 | # Executable returns non-zero exit code when the help is displayed, so we have to ignore it 25 | readme.txt: $(EXECUTABLE) 26 | $(EXECUTABLE) > readme.txt || true 27 | 28 | $(EXECUTABLE): $(wildcard $(SRC_DIR)/*.cpp) $(wildcard $(LIB_DIR)/*.hpp) 29 | $(EXECUTABLE): | $(BUILD_DIR) 30 | $(CXX) $(CXXFLAGS) $(LDFLAGS) $(SRC_DIR)/main.cpp -o $(EXECUTABLE) 31 | 32 | $(BUILD_DIR): 33 | mkdir $(BUILD_DIR) 34 | -------------------------------------------------------------------------------- /utils/cbundle/Makefile.win: -------------------------------------------------------------------------------- 1 | 2 | BUILD_DIR ?= ..\..\build\utils 3 | SRC_DIR ?= src 4 | LIB_DIR ?= ..\lib 5 | 6 | EXEC_NAME = cbundle.exe 7 | EXECUTABLE = $(BUILD_DIR)\$(EXEC_NAME) 8 | 9 | CXXFLAGS ?= -std=c++20 -O2 -pedantic-errors -Wall -I..\lib 10 | LDFLAGS ?= -s -static 11 | 12 | .PHONY: all cbundle test clean 13 | 14 | all: cbundle readme.txt 15 | 16 | cbundle: $(EXECUTABLE) 17 | 18 | test: $(EXECUTABLE) 19 | cd tests && python3 test.py 20 | 21 | clean: 22 | del /q /f "$(EXECUTABLE)" 23 | 24 | # Executable returns non-zero exit code when the help is displayed, so we have to ignore it 25 | readme.txt: $(EXECUTABLE) 26 | $(EXECUTABLE) > readme.txt || echo true 27 | 28 | $(EXECUTABLE): $(wildcard $(SRC_DIR)\*.cpp) $(wildcard $(LIB_DIR)\*.hpp) 29 | $(EXECUTABLE): | $(BUILD_DIR) 30 | $(CXX) $(CXXFLAGS) $(LDFLAGS) $(SRC_DIR)\main.cpp -o $(EXECUTABLE) 31 | 32 | $(BUILD_DIR): 33 | -md $(BUILD_DIR) 34 | -------------------------------------------------------------------------------- /utils/cbundle/readme.txt: -------------------------------------------------------------------------------- 1 | CBundle utility version 2.0.1 2 | 2017-2023, vladikcomper 3 | 4 | Command line arguments: 5 | cbundle [script_file_path|-] [OPTIONS] 6 | 7 | NOTICE: Using "-" as a script file path redirects input to stdin. 8 | 9 | OPTIONS: 10 | -out [output_file_path|-] 11 | If set, writes output to the given path, unless overriden by #file directive. Using - will redirect to stdout. 12 | 13 | -def [symbol] 14 | Pre-defines a symbol with the given, equivalent to #def [symbol] directive. To specify several symbols, repeat -def [symbol] as many times as needed. 15 | 16 | -cwd [dir] 17 | If set, changes current working directory to [dir]. Path can be relative. 18 | 19 | -debug 20 | Enable debug output. 21 | 22 | SUPPORTED DIRECTIVES: 23 | 24 | #define 25 | Defines a symbol. 26 | 27 | #undef 28 | Removes a symbol from defined symbols list. 29 | 30 | #include 31 | Opens the specified file and executes its directives. 32 | 33 | #file 34 | Creates or rewrites a file, directs all the output to this file. 35 | 36 | #endf 37 | Finishes writing to previously opened file. 38 | 39 | #ifdef 40 | Enters IF-block if symbol was defined previously. 41 | 42 | #ifndef 43 | Enters IF-block if symbol wasn't defined previously. 44 | 45 | #else 46 | Enters ELSE-block if the IF-block's condition wasn't met. 47 | 48 | #endif 49 | Ends IF-ELSE-block. 50 | -------------------------------------------------------------------------------- /utils/cbundle/src/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* ------------------------------------------------------------ * 3 | * Bundle Compilation utility v.2.0.1 * 4 | * * 5 | * Main definitions file * 6 | * (c) 2017-2023, Vladikcomper * 7 | * ------------------------------------------------------------ */ 8 | 9 | // Standard C-libraries 10 | #include // for I/O operations and file accesses 11 | #include // for uint8_t, uint16_t, etc. 12 | #include // for va_start, va_end, etc. 13 | #include // for strlen, strncspn, etc. 14 | #include 15 | 16 | // Standard C++ libraries 17 | #include // for strings processing 18 | #include // standard containers 19 | #include // '' 20 | #include // '' 21 | 22 | // Helper classes 23 | #include 24 | #include 25 | 26 | #include "parser.cpp" 27 | 28 | /* Main function */ 29 | int main (int argc, const char ** argv) { 30 | 31 | /* Provide help if called without enough options */ 32 | if (argc<2) { 33 | printf( 34 | "CBundle utility version 2.0.1\n" 35 | "2017-2023, vladikcomper\n" 36 | "\n" 37 | "Command line arguments:\n" 38 | " cbundle [script_file_path|-] [OPTIONS]\n" 39 | "\n" 40 | "NOTICE: Using \"-\" as a script file path redirects input to stdin.\n" 41 | "\n" 42 | "OPTIONS:\n" 43 | " -out [output_file_path|-]\n" 44 | " If set, writes output to the given path, unless overriden by #file directive. Using - will redirect to stdout.\n" 45 | "\n" 46 | " -def [symbol]\n" 47 | " Pre-defines a symbol with the given, equivalent to #def [symbol] directive. To specify several symbols, repeat -def [symbol] as many times as needed.\n" 48 | "\n" 49 | " -cwd [dir]\n" 50 | " If set, changes current working directory to [dir]. Path can be relative.\n" 51 | "\n" 52 | " -debug\n" 53 | " Enable debug output.\n" 54 | "\n" 55 | "SUPPORTED DIRECTIVES:\n" 56 | "\n" 57 | " #define \n" 58 | " Defines a symbol.\n" 59 | "\n" 60 | " #undef \n" 61 | " Removes a symbol from defined symbols list.\n" 62 | "\n" 63 | " #include \n" 64 | " Opens the specified file and executes its directives.\n" 65 | "\n" 66 | " #file \n" 67 | " Creates or rewrites a file, directs all the output to this file.\n" 68 | "\n" 69 | " #endf\n" 70 | " Finishes writing to previously opened file.\n" 71 | "\n" 72 | " #ifdef \n" 73 | " Enters IF-block if symbol was defined previously.\n" 74 | "\n" 75 | " #ifndef \n" 76 | " Enters IF-block if symbol wasn't defined previously.\n" 77 | "\n" 78 | " #else\n" 79 | " Enters ELSE-block if the IF-block's condition wasn't met.\n" 80 | "\n" 81 | " #endif\n" 82 | " Ends IF-ELSE-block.\n" 83 | ); 84 | return 1; 85 | } 86 | 87 | /* Parse command line arguments */ 88 | const char *inputFileName = argv[1]; 89 | std::string outputFileName = ""; 90 | std::string currentPathOverride = ""; 91 | std::vector predefinedSymbols; 92 | 93 | bool optDebug = false; 94 | { 95 | const std::map 96 | ParametersList { 97 | { "-debug", { .type = ArgvParser::record::flag, .target = &optDebug } }, 98 | { "-cwd", { .type = ArgvParser::record::string, .target = ¤tPathOverride } }, 99 | { "-out", { .type = ArgvParser::record::string, .target = &outputFileName } }, 100 | { "-def", { .type = ArgvParser::record::string_list, .target = &predefinedSymbols } }, 101 | }; 102 | 103 | /* Decode parameters acording to list defined by "ParametersList" variable */ 104 | try { 105 | ArgvParser::parse( argv+2, argc-2, ParametersList ); 106 | } 107 | catch (const char* err) { 108 | IO::Log( IO::fatal, err ); 109 | return -1; 110 | } 111 | } 112 | 113 | IO::LogLevel = optDebug ? IO::debug : IO::warning; 114 | 115 | /* Pre-define symbols of requested */ 116 | if (!predefinedSymbols.empty()) { 117 | for (auto symbol : predefinedSymbols) { 118 | Parser::symbols.insert(symbol); 119 | } 120 | } 121 | 122 | /* Override current working directory if requested */ 123 | if (!currentPathOverride.empty()) { 124 | std::filesystem::current_path(std::filesystem::absolute(currentPathOverride)); 125 | } 126 | 127 | /* Process input file */ 128 | bool result = false; 129 | if (!outputFileName.empty()) { 130 | Parser::parseData out = { 131 | .file = new IO::FileOutput(outputFileName.c_str()), 132 | .fileName = outputFileName.c_str(), 133 | .lineNumber = 0 134 | }; 135 | result = Parser::parseFile( inputFileName, &out ); 136 | } 137 | else { 138 | result = Parser::parseFile( inputFileName ); 139 | } 140 | 141 | if (result == false) { 142 | IO::Log( IO::fatal, "Bundle generation failed." ); 143 | return -1; 144 | } 145 | 146 | return 0; 147 | 148 | } 149 | -------------------------------------------------------------------------------- /utils/cbundle/tests/.gitignore: -------------------------------------------------------------------------------- 1 | output/* 2 | !output/.gitkeep -------------------------------------------------------------------------------- /utils/cbundle/tests/input/sample-common-include.txt: -------------------------------------------------------------------------------- 1 | This is contents included from common-include.txt file: 2 | ## This is a comment and should be skipped upon 3 | #ifdef TestSymbol 4 | TestSymbol was defined in global scope! 5 | #endif 6 | End of contents from common-include.txt file 7 | -------------------------------------------------------------------------------- /utils/cbundle/tests/input/sample.cbundle: -------------------------------------------------------------------------------- 1 | ############################ 2 | ## CBundle testing script ## 3 | ############################ 4 | 5 | This raw line should be skipped as not part of any file... 6 | 7 | #file output/sample-file-1.txt 8 | This file contains only raw strings ... 9 | ... and no directives. 10 | #endf 11 | 12 | ## Define some symbols ... 13 | #define TestSymbol 14 | 15 | #file output/sample-file-2.txt 16 | #include input/sample-common-include.txt 17 | #ifdef TestSymbol2 18 | This text AND the following block should have no effect... 19 | #ifdef 1 20 | #else 21 | #endif 22 | ... still no effect. 23 | #else 24 | TestSymbol2 wasn't defined (reported by #ifdef > #else) 25 | #endif 26 | ## The following command should have no effect: 27 | #undef TestSymbol2 28 | #ifndef TestSymbol2 29 | TestSymbol2 wasn't defined (reported by #ifndef) 30 | #endif 31 | #define Test 32 | #ifdef Test 33 | Test was defined! 34 | #endif 35 | #undef Test 36 | #ifndef Test 37 | Test wasn't defined 38 | #endif 39 | #endf 40 | -------------------------------------------------------------------------------- /utils/cbundle/tests/output-expected/sample-file-1.txt: -------------------------------------------------------------------------------- 1 | This file contains only raw strings ... 2 | ... and no directives. 3 | -------------------------------------------------------------------------------- /utils/cbundle/tests/output-expected/sample-file-2.txt: -------------------------------------------------------------------------------- 1 | This is contents included from common-include.txt file: 2 | TestSymbol was defined in global scope! 3 | End of contents from common-include.txt file 4 | TestSymbol2 wasn't defined (reported by #ifdef > #else) 5 | TestSymbol2 wasn't defined (reported by #ifndef) 6 | Test was defined! 7 | Test wasn't defined 8 | -------------------------------------------------------------------------------- /utils/cbundle/tests/output/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/utils/cbundle/tests/output/.gitkeep -------------------------------------------------------------------------------- /utils/cbundle/tests/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from dataclasses import dataclass 3 | from typing import cast 4 | from pathlib import Path 5 | import subprocess 6 | 7 | import sys 8 | sys.path.append('../../lib-py') 9 | from test_framework import Test, Command, CommandResult, DataSource, File, CheckMatch, Buffer, Void, archiveDirectory, unarchiveDirectory, getUsedFilesList, runTests 10 | 11 | CBUNDLE_PATH = '../../../build/utils/cbundle' 12 | 13 | @dataclass 14 | class CBundle(Command): 15 | options: 'tuple[str, ...]' = () 16 | 17 | def execute(self, input: DataSource, output: DataSource) -> CommandResult: 18 | use_stdin = not isinstance(input, File) 19 | use_stdout = not isinstance(output, File) 20 | 21 | # Launch CBundle 22 | args = [CBUNDLE_PATH, '-', *self.options] 23 | args[1] = '-' if use_stdin else str(cast(File, input).getPath()) 24 | if not isinstance(output, Void): 25 | args += ['-out', '-' if use_stdout else str(cast(File, output).getPath())] 26 | 27 | result = subprocess.run(args, input=None if isinstance(input, File) else input.read(), text=False, capture_output=True) 28 | 29 | # Collect output 30 | if result.returncode != 0: return (False, f'CBundle returned non-zero exit code (stderr="{result.stderr}")') 31 | if use_stdout: output.write(result.stdout) 32 | return (True, output) 33 | 34 | tests: 'tuple[Test, ...]' = ( 35 | Test( 36 | description = 'One static line', 37 | pipeline=( 38 | CBundle( 39 | input = Buffer(b'Hello world!\n'), 40 | ), 41 | CheckMatch( 42 | output=Buffer(b'Hello world!\n'), 43 | text=True 44 | ), 45 | ), 46 | ), 47 | Test( 48 | description = 'Undefined symbol test', 49 | pipeline=( 50 | CBundle( 51 | input = Buffer(b'#ifdef SYMBOL\nSYMBOL IS DEFINED\n#endif\n'), 52 | ), 53 | CheckMatch( 54 | output=Buffer(b''), 55 | text=True 56 | ), 57 | ), 58 | ), 59 | Test( 60 | description = 'Pre-defined symbol test', 61 | pipeline=( 62 | CBundle( 63 | input = Buffer(b'#ifdef SYMBOL\nSYMBOL IS DEFINED\n#endif\n'), 64 | options=('-def', 'SYMBOL'), 65 | ), 66 | CheckMatch( 67 | output=Buffer(b'SYMBOL IS DEFINED\n'), 68 | text=True 69 | ), 70 | ), 71 | ), 72 | Test( 73 | description = 'Several pre-defined symbols', 74 | pipeline=( 75 | CBundle( 76 | input = Buffer(b'#ifdef SYMBOL1\nSYMBOL1 IS DEFINED\n#endif\n#ifdef SYMBOL2\nSYMBOL2 IS DEFINED\n#endif\n'), 77 | options=('-def', 'SYMBOL1', '-def', 'SYMBOL2'), 78 | ), 79 | CheckMatch( 80 | output=Buffer(b'SYMBOL1 IS DEFINED\nSYMBOL2 IS DEFINED\n'), 81 | text=True 82 | ), 83 | ), 84 | ), 85 | Test( 86 | description = 'Generating a sample bundle', 87 | pipeline=( 88 | CBundle( 89 | input = File('input/sample.cbundle'), 90 | ), 91 | CheckMatch( 92 | input=File('output/sample-file-1.txt'), 93 | output=File('output-expected/sample-file-1.txt'), 94 | text=True 95 | ), 96 | CheckMatch( 97 | input=File('output/sample-file-2.txt'), 98 | output=File('output-expected/sample-file-2.txt'), 99 | text=True 100 | ), 101 | ), 102 | ), 103 | ) 104 | 105 | def main(): 106 | success = runTests(tests) 107 | 108 | if not success: 109 | exit(1) 110 | 111 | if __name__ == '__main__': 112 | main() 113 | -------------------------------------------------------------------------------- /utils/convsym/Makefile: -------------------------------------------------------------------------------- 1 | 2 | BUILD_DIR ?= ../../build/utils 3 | SRC_DIR ?= src 4 | LIB_DIR ?= ../lib 5 | 6 | EXEC_NAME = convsym 7 | EXECUTABLE = $(BUILD_DIR)/$(EXEC_NAME) 8 | 9 | CXXFLAGS ?= -std=c++20 -O2 -pedantic-errors -Wall -I$(LIB_DIR) 10 | LDFLAGS ?= -s 11 | 12 | .PHONY: all convsym test clean 13 | 14 | all: convsym 15 | 16 | convsym: $(EXECUTABLE) 17 | 18 | test: $(EXECUTABLE) 19 | cd tests && ./test.py 20 | 21 | clean: 22 | rm $(EXECUTABLE) 23 | 24 | $(EXECUTABLE): $(wildcard $(SRC_DIR)/*.cpp) $(wildcard $(SRC_DIR)/**/*.cpp) $(wildcard $(SRC_DIR)/**/*.hpp) $(wildcard $(LIB_DIR)/*.hpp) 25 | $(EXECUTABLE): | $(BUILD_DIR) 26 | $(CXX) $(CXXFLAGS) $(LDFLAGS) $(SRC_DIR)/main.cpp -o $(EXECUTABLE) 27 | 28 | $(BUILD_DIR): 29 | mkdir $(BUILD_DIR) 30 | -------------------------------------------------------------------------------- /utils/convsym/Makefile.win: -------------------------------------------------------------------------------- 1 | 2 | BUILD_DIR ?= ..\..\build\utils 3 | SRC_DIR ?= src 4 | LIB_DIR ?= ..\lib 5 | 6 | EXEC_NAME = convsym.exe 7 | EXECUTABLE = $(BUILD_DIR)\$(EXEC_NAME) 8 | 9 | CXXFLAGS ?= -std=c++20 -O2 -pedantic-errors -Wall -I$(LIB_DIR) 10 | LDFLAGS ?= -s -static 11 | 12 | .PHONY: all convsym test clean 13 | 14 | all: convsym 15 | 16 | convsym: $(EXECUTABLE) 17 | 18 | test: $(EXECUTABLE) 19 | cd tests && python3 test.py 20 | 21 | clean: 22 | del /q /f "$(EXECUTABLE)" 23 | 24 | $(EXECUTABLE): $(wildcard $(SRC_DIR)\*.cpp) $(wildcard $(SRC_DIR)\**\*.cpp) $(wildcard $(SRC_DIR)\**\*.hpp) $(wildcard $(LIB_DIR)\*.hpp) 25 | $(EXECUTABLE): | $(BUILD_DIR) 26 | $(CXX) $(CXXFLAGS) $(LDFLAGS) $(SRC_DIR)\main.cpp -o $(EXECUTABLE) 27 | 28 | $(BUILD_DIR): 29 | -md $(BUILD_DIR) 30 | -------------------------------------------------------------------------------- /utils/convsym/docs/DEB2_Format.txt: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------- 3 | Debug Information version 2.0 format 4 | Created and documented by Vladikcomper 5 | --------------------------------------- 6 | 7 | The debug information data includes 3 base sections, as listed below: 8 | 1. Header 9 | 2. Huffman decoding table 10 | 3. Blocks 11 | 12 | ---------- 13 | 1. Header 14 | ---------- 15 | 16 | $00 .w Version token, should contain $DEB2 so the format is recognized as valid 17 | $02 .w Relative* pointer to the Huffman decoding table 18 | 19 | Below this and before the Huffman decoding table, there is array of blocks pointers, the layout is demonstrated below: 20 | 21 | $04 .l Relative pointer to block $00 data (covers offsets $0 - $FFFF) or zero if the block has no symbols 22 | $06 .l Relative pointer to block $01 data (covers offsets $10000 - $1FFFF) or zero if the block has no symbols 23 | ... 24 | 25 | Block pointer table notes: 26 | 1) The number of blocks is determined by the ConvSym utility based on symbol with the greatest offset found within the valid memory range (its value shouldn't exceed the ROM size). 27 | 2) Each block covers the respective 64 kb section of ROM: block $00 is the first 64kb, block $01 is the second 64kb, etc. 28 | 4) As stated above, if the block doesn't happen to include any symbols, the pointer is zeroed to indicate this. 29 | 30 | *) Pointer is relative to its own offset, not the offset of the section itself. E.g. pointer containing $0020 means the data is $20 bytes below the location, where the pointer itself was read from. Pointers are also considered to be unsigned, meaning that $FFFE (-$0002 in signed form) actually adds $FFFE to the location, and not subtracts $0002 from it. 31 | 32 | --------------------------- 33 | 2. Huffman decoding table 34 | --------------------------- 35 | 36 | This table is used to decode Huffman-compressed symbol data. The table consists of the records with the following format: 37 | 38 | $00 .w Code 39 | $02 .b Code length 40 | $03 .b Decoded character 41 | 42 | Note that all entries must be sorted ascendingly by the code field, to make faster search possible. 43 | 44 | ----------- 45 | 3. Blocks 46 | ----------- 47 | 48 | Each 64kb block structure starts with the word, specifying the length of the main data structures describing each symbol within the block: 49 | 50 | $00 .w Relative pointer to the symbols heap 51 | 52 | Below this and before the heap follows the array of symbol structures, each record having the following format: 53 | 54 | $00 .w Lower 16-bit of symbol offset (higher 16-bits are determined by the block number itself) 55 | $02 .w Pointer to the encoded symbol text, relative to the symbols heap start 56 | 57 | Corresponding symbol texts are usually stored in a so-called "symbols heap", which should be located right after structures array. 58 | 59 | -------------------------------------------------------------------------------- /utils/convsym/src/input/ASM68K_Sym.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* ------------------------------------------------------------ * 3 | * ConvSym utility version 2.12 * 4 | * Input wrapper for the ASM68K compiler's symbol format * 5 | * ------------------------------------------------------------ */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "InputWrapper.hpp" 15 | 16 | 17 | struct Input__ASM68K_Sym : public InputWrapper { 18 | 19 | Input__ASM68K_Sym() {} 20 | ~Input__ASM68K_Sym() {} 21 | 22 | void parse(SymbolTable& symbolTable, const char *fileName, const char * opts) { 23 | IO::FileInput input = IO::FileInput(fileName); 24 | if (!input.good()) { throw "Couldn't open input file"; } 25 | 26 | // Supported options: 27 | // /localSign=x - determines character used to specify local labels 28 | // /localJoin=x - character used to join local label and its global "parent" 29 | // /processLocals? - specify whether local labels will processed 30 | 31 | // Default processing options 32 | bool optProcessLocalLabels = true; 33 | 34 | // Variables and options 35 | char localLabelSymbol = '@'; // default symbol for local labels 36 | char localLabelRef = '.'; // default symbol to reference local labels within global ones 37 | 38 | const std::map 39 | OptsList { 40 | { "localSign", { .type = OptsParser::record::p_char, .target = &localLabelSymbol } }, 41 | { "localJoin", { .type = OptsParser::record::p_char, .target = &localLabelRef } }, 42 | { "processLocals", { .type = OptsParser::record::p_bool, .target = &optProcessLocalLabels } } 43 | }; 44 | 45 | OptsParser::parse(opts, OptsList); 46 | 47 | // NOTICE: Symbols are usually written OUT OF ORDER in the symbols file, 48 | // so we have to map them first before filtering 49 | std::multimap UnfilteredSymbolsMap; 50 | input.setOffset(0x0008); 51 | 52 | for(;;) { 53 | uint32_t offset; 54 | try { // read 32-bit label offset 55 | offset = input.readLong(); 56 | } catch(...) { // if reading failed, break 57 | break; 58 | } 59 | 60 | input.setOffset( 1, IO::current ); // skip 1 byte 61 | 62 | const size_t labelLength = (size_t)input.readByte(); 63 | char sLabel[255]; 64 | input.readData((uint8_t*)&sLabel, labelLength); // read label 65 | 66 | UnfilteredSymbolsMap.insert({ offset, std::string((const char*)&sLabel, labelLength)}); 67 | } 68 | 69 | // Now we can properly process symbols list IN ORDER 70 | const std::string *lastGlobalLabelRef = &UnfilteredSymbolsMap.cbegin()->second; // default global label name 71 | 72 | for (const auto & [offset, label]: UnfilteredSymbolsMap) { 73 | if (label[0] == localLabelSymbol) { 74 | // Ignore local labels if "processLocals" is disabled 75 | if ( !optProcessLocalLabels ) { 76 | IO::Log(IO::debug, "Local symbol ignored: %s", label.c_str()); 77 | continue; 78 | } 79 | 80 | std::string fullLabel = *lastGlobalLabelRef + localLabelRef + label.substr(1); 81 | symbolTable.add(offset, fullLabel); 82 | } 83 | else { 84 | lastGlobalLabelRef = &label; 85 | symbolTable.add(offset, label); 86 | } 87 | } 88 | } 89 | 90 | }; 91 | 92 | -------------------------------------------------------------------------------- /utils/convsym/src/input/AS_Listing.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* ------------------------------------------------------------ * 3 | * ConvSym utility version 2.12 * 4 | * Input wrapper for the AS listing format * 5 | * ------------------------------------------------------------ */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include "InputWrapper.hpp" 17 | 18 | 19 | struct Input__AS_Listing : public InputWrapper { 20 | 21 | Input__AS_Listing() {} 22 | ~Input__AS_Listing() {} 23 | 24 | void parse(SymbolTable& symbolTable, const char *fileName, const char * opts) { 25 | 26 | // Default processing options 27 | bool optProcessLocalLabels = true; 28 | bool optIgnoreInternalSymbols = true; 29 | 30 | // Variables 31 | char localLabelSymbol = '.'; // default symbol for local labels 32 | bool foundSymbolTable = false; 33 | 34 | // Fetch options from "-inopt" argument's value 35 | const std::map 36 | OptsList { 37 | { "localJoin", { .type = OptsParser::record::p_char, .target = &localLabelSymbol } }, 38 | { "processLocals", { .type = OptsParser::record::p_bool, .target = &optProcessLocalLabels } }, 39 | { "ignoreInternalSymbols", { .type = OptsParser::record::p_bool, .target = &optIgnoreInternalSymbols } } 40 | }; 41 | 42 | // Setup buffer and file for input 43 | const int sBufferSize = 1024; 44 | uint8_t sBuffer[ sBufferSize ]; 45 | IO::FileInput input = IO::FileInput( fileName, IO::text ); 46 | if ( !input.good() ) { throw "Couldn't open input file"; } 47 | 48 | // For every string in a listing file ... 49 | for ( 50 | int lineCounter = 0, lineLength; 51 | lineLength = input.readLine( sBuffer, sBufferSize ), lineLength >= 0; 52 | ++lineCounter 53 | ) { 54 | // If line is too short, do not proceed 55 | if (lineLength < 8) { 56 | continue; 57 | } 58 | 59 | std::string_view strLine((char*)sBuffer, (size_t)lineLength); 60 | 61 | // Phase 1: Search for symbol table header ... 62 | if (!foundSymbolTable) { 63 | // Trim whitespace from the beginning ... 64 | strLine.remove_prefix( 65 | std::min(strLine.find_first_not_of(" \t"), strLine.size()) 66 | ); 67 | // Newer versions of AS seem to output "Symbol Table" string instead of "symbol table" 68 | if (strLine.starts_with("symbol table") || strLine.starts_with("Symbol Table")) { 69 | foundSymbolTable = true; 70 | 71 | IO::Log(IO::debug, "Found symbols table header on line %d", lineCounter); 72 | } 73 | } 74 | 75 | // Phase 2: Parse the symbol table 76 | else { 77 | // If line include table separator '|', process cells and extract labels and offsets from them ... 78 | // NOTICE: This loop won't yield any results if '|' is absent completely. 79 | for ( 80 | size_t left = 0, right = strLine.find_first_of('|'); 81 | right != std::string_view::npos; 82 | left = right + 1, right = strLine.find_first_of('|', left) 83 | ) { 84 | const auto maybeSymbol = this->parseSymbolTableEntry(strLine.substr(left, right-left), optProcessLocalLabels, localLabelSymbol); 85 | 86 | if (maybeSymbol.has_value()) { 87 | auto symbol = maybeSymbol.value(); 88 | 89 | if (optIgnoreInternalSymbols && symbol.second.starts_with("__")) continue; 90 | 91 | symbolTable.add(symbol.first, symbol.second); 92 | } 93 | } 94 | } 95 | } 96 | 97 | if (!foundSymbolTable) { 98 | throw "Coudn't find symbols table"; 99 | } 100 | } 101 | 102 | private: 103 | std::optional> parseSymbolTableEntry(const std::string_view &strEntry, bool optProcessLocalLabels, char localLabelSymbol) { 104 | #define IS_HEX_CHAR(X) ((unsigned)(X-'0')<10||(unsigned)(X-'A')<6) 105 | #define IS_START_OF_LABEL(X) ((unsigned)(X-'A')<26||(unsigned)(X-'a')<26||X=='_') 106 | #define IS_LABEL_CHAR(X) ((unsigned)(X-'A')<26||(unsigned)(X-'a')<26||(optProcessLocalLabels&&X==localLabelSymbol)||(unsigned)(X-'0')<10||X=='_') 107 | #define IS_WHITESPACE(X) (X==' '||X=='\t') 108 | 109 | const auto end = strEntry.cend(); 110 | auto it = strEntry.cbegin(); 111 | 112 | // Skip whitespace at the beginning ... 113 | while ( it != end && (IS_WHITESPACE(*it) || *it == '*') ) ++it; 114 | 115 | // Capture label ... 116 | if ( it == end || !(IS_START_OF_LABEL(*it)) ) return std::nullopt; 117 | const auto labelBegin = it++; 118 | while ( it != end && IS_LABEL_CHAR(*it) ) ++it; 119 | const auto labelEnd = it; 120 | 121 | // Skip " : " and following whitespace 122 | if ( it == end || *it++ != ' ' ) return std::nullopt; 123 | if ( it == end || *it++ != ':' ) return std::nullopt; 124 | while ( it != end && IS_WHITESPACE(*it) ) ++it; 125 | 126 | // Capture offset ... 127 | if ( it == end || !(IS_HEX_CHAR(*it)) ) return std::nullopt; 128 | uint32_t offset = 0; 129 | while ( it != end && IS_HEX_CHAR(*it) ) { 130 | offset = offset * 0x10 + (((unsigned)(*it-'0')<10) ? (*it-'0') : (*it-('A'-10))); 131 | ++it; 132 | } 133 | 134 | // Capture label type ... 135 | if ( it == end || *it++ != ' ' ) return std::nullopt; 136 | if ( it == end || *it++ != 'C' ) return std::nullopt; 137 | 138 | // Return results 139 | return std::optional>{ { offset, std::string(labelBegin, labelEnd) } }; 140 | 141 | #undef IS_HEX_CHAR 142 | #undef IS_START_OF_LABEL 143 | #undef IS_LABEL_CHAR 144 | #undef IS_WHITESPACE 145 | } 146 | }; 147 | -------------------------------------------------------------------------------- /utils/convsym/src/input/AS_Listing_Experimental.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* ------------------------------------------------------------ * 3 | * ConvSym utility version 2.12 * 4 | * Input wrapper for the AS listing format * 5 | * ------------------------------------------------------------ */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "InputWrapper.hpp" 14 | 15 | 16 | struct Input__AS_Listing_Experimental : public InputWrapper { 17 | 18 | Input__AS_Listing_Experimental() {} 19 | ~Input__AS_Listing_Experimental() {} 20 | 21 | void parse(SymbolTable& symbolTable, const char *fileName, const char * opts) { 22 | const int sBufferSize = 1024; 23 | if (*opts) { 24 | IO::Log(IO::warning, "-inopt is not supported by this parser"); 25 | } 26 | 27 | // Variables 28 | uint8_t sBuffer[ sBufferSize ]; 29 | std::multimap SymbolMap; 30 | IO::FileInput input = IO::FileInput( fileName, IO::text ); 31 | if ( !input.good() ) { throw "Couldn't open input file"; } 32 | uint32_t lastSymbolOffset = -1; // tracks symbols offsets to ignore sections where PC is reset (mainly Z80 stuff) 33 | 34 | // For every string in a listing file ... 35 | while ( input.readLine( sBuffer, sBufferSize ) >= 0 ) { 36 | 37 | // Known issues for the Sonic 2 disassembly: 38 | // * Some macros somehow define labels that looks like global ones (notably, _MOVE and such) 39 | // * Labels from injected Z80 code sometimes make it to the list ... 40 | 41 | uint8_t* ptr = sBuffer; // WARNING: dereffed type should be UNSIGNED, as it's used for certain range-based optimizations 42 | 43 | // Check if this line has file idention number (pattern: "(d)", where d is a decimal digit) 44 | if ( *ptr == '(' ) { 45 | bool foundDigits = false; 46 | ptr++; 47 | 48 | // Cycle through as long as found characters are digits 49 | while ( (unsigned)(*ptr-'0')<10 ) { foundDigits=true; ptr++; } 50 | 51 | // If digit pattern doesn't end with ")" as expected, or no digits were found at all, stop 52 | if ( *ptr++ != ')' || !foundDigits ) continue; 53 | } 54 | 55 | // Ensure line has a proper number (pattern: "ddddd/", where d is any digit) 56 | { 57 | bool foundDigits = false; 58 | while ( *ptr == ' ' ) { ptr++; } // skip spaces, if present 59 | 60 | // Cycle through as long as found characters are digits 61 | while ( (unsigned)(*ptr-'0')<10 ) { foundDigits=true; ptr++; } 62 | 63 | // If digit pattern doesn't end with "/" as expected, or no digits were found at all, stop 64 | if ( *ptr++ != '/' || !foundDigits ) continue; 65 | } 66 | 67 | // Ensure line has a proper offset (pattern: "hhhh :", where h is a hexidecimal character) 68 | while ( *ptr == ' ' ) { ptr++; } // skip spaces, if present 69 | uint8_t* const sOffset = ptr; // remember location, where offset should presumably start 70 | { 71 | bool foundHexDigits = false; 72 | 73 | // Cycle though as longs as proper hexidecimal characters are fetched 74 | while ( (unsigned)(*ptr-'0')<10 || (unsigned)(*ptr-'A')<6 ) { 75 | foundHexDigits = true; 76 | ptr++; 77 | } 78 | 79 | if ( *ptr == 0x00 ) continue; // break if end of line was reached 80 | *ptr++ = 0x00; // separate string pointered by "offset" variable from the rest of the line 81 | while ( *ptr == ' ' ) { ptr++; } // skip spaces ... 82 | 83 | // If offset pattern doesn't end with ":" as expected, or no hex characters were found at all, stop 84 | if ( *ptr++ != ':' || !foundHexDigits ) continue; 85 | } 86 | 87 | // Check if line matches a label definition ... 88 | if ( *ptr == ' ' && *(ptr+1) == '(' ) continue; 89 | while ( *ptr && (ptr-sBuffer < 40) ) { ptr++; } // align to column 40 (where line starts) 90 | while ( *ptr==' ' || *ptr=='\t' ) { ptr++; } // skip spaces or tabs, if present 91 | uint8_t* const label = ptr; // remember location, where label presumably starts... 92 | 93 | // The first character should be a latin letter (A..Z or a..z) 94 | if ( (unsigned)(*ptr-'A')<26 || (unsigned)(*ptr-'a')<26 ) { 95 | 96 | // Other characters should be letters are digits or _ char 97 | while ( 98 | (unsigned)(*ptr-'A')<26 || (unsigned)(*ptr-'a')<26 || (unsigned)(*ptr-'0')<10 || *ptr=='_' 99 | ) { ptr++; } 100 | 101 | // If the word is the only on the line and it ends with ":", treat it as a label 102 | if ( *ptr==':' ) { 103 | *ptr++ = 0x00; // separate string pointered by "label" variable from the rest of the line 104 | 105 | // Convert offset to uint32_t 106 | uint32_t offset = 0; 107 | for ( uint8_t* c = sOffset; *c; c++ ) { 108 | offset = offset*0x10 + (((unsigned)(*c-'0')<10) ? (*c-'0') : (*c-('A'-10))); 109 | } 110 | 111 | // Add label to the symbols table, if: 112 | // 1) Its absolute offset is higher than the previous offset successfully added 113 | // 2) When base offset is subtracted and the mask is applied, the resulting offset is within allowed boundaries 114 | if ( (lastSymbolOffset == (uint32_t)-1) || (offset >= lastSymbolOffset) ) { 115 | // Check if this is a label after ds or rs macro ... 116 | while ( *ptr==' ' || *ptr=='\t' ) { ptr++; } // skip spaces or tabs, if present 117 | if ( *ptr == 'd' && *(ptr+1) == 's' && *(ptr+2)=='.' ) continue; 118 | if ( *ptr == 'r' && *(ptr+1) == 's' && *(ptr+2)=='.' ) continue; 119 | 120 | const bool inserted = symbolTable.add(offset, (const char*)label); 121 | if (inserted) { 122 | lastSymbolOffset = offset; 123 | } 124 | } 125 | else { 126 | IO::Log( IO::debug, "Symbol %s at offset %X ignored: its offset is less than the previous symbol successfully fetched", label, offset ); 127 | } 128 | } 129 | } 130 | } 131 | } 132 | 133 | }; 134 | -------------------------------------------------------------------------------- /utils/convsym/src/input/InputWrapper.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "../util/SymbolTable.hpp" 9 | 10 | /* Base class for the input formats handlers */ 11 | struct InputWrapper { 12 | InputWrapper() {} 13 | virtual ~InputWrapper() {} 14 | 15 | // Virtual function interface that handles input file parsing 16 | virtual void parse( 17 | SymbolTable& symbolTable, 18 | const char *fileName, 19 | const char *opts 20 | ) = 0; 21 | }; 22 | -------------------------------------------------------------------------------- /utils/convsym/src/input/Log.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* ------------------------------------------------------------ * 3 | * ConvSym utility version 2.12 * 4 | * Input wrapper for log files * 5 | * ------------------------------------------------------------ */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include "InputWrapper.hpp" 16 | 17 | 18 | struct Input__Log : public InputWrapper { 19 | 20 | Input__Log() {} 21 | ~Input__Log() {} 22 | 23 | void parse(SymbolTable& symbolTable, const char *fileName, const char * opts = "") { 24 | // Supported options: 25 | // /separator=x - determines character that separates labes and offsets, default: ":" 26 | // /useDecimal? - set if offsets should be treat as decimal numbers; default: - 27 | 28 | // Variables and options 29 | char labelSeparator = ':'; 30 | bool optUseDecimal = false; 31 | 32 | const std::map 33 | OptsList { 34 | { "separator", { .type = OptsParser::record::p_char, .target = &labelSeparator } }, 35 | { "useDecimal", { .type = OptsParser::record::p_bool, .target = &optUseDecimal } } 36 | }; 37 | 38 | OptsParser::parse( opts, OptsList ); 39 | 40 | // Setup buffer, symbols list and file for input 41 | const int sBufferSize = 1024; 42 | uint8_t sBuffer[ sBufferSize ]; 43 | std::multimap SymbolMap; 44 | IO::FileInput input = IO::FileInput( fileName, IO::text ); 45 | if ( !input.good() ) { 46 | throw "Couldn't open input file"; 47 | } 48 | 49 | // Define re-usable conditions 50 | #define IS_HEX_CHAR(X) ((unsigned)(X-'0')<10||(unsigned)(X-'A')<6||(unsigned)(X-'a')<6) 51 | #define IS_NUMERIC(X) ((unsigned)(X-'0')<10) 52 | #define SKIP_SPACES(X) while ( *X==' ' || *X=='\t' ) X++ 53 | 54 | int lineNum = 0; 55 | while (input.readLine( sBuffer, sBufferSize ) >= 0) { 56 | lineNum++; 57 | 58 | uint8_t* ptr = sBuffer; // WARNING: Unsigned type is required here for certain range-based optimizations 59 | 60 | SKIP_SPACES(ptr); 61 | 62 | // Decode the offset ... 63 | uint32_t offset = 0; 64 | if ( optUseDecimal ) { 65 | while ( IS_NUMERIC(*ptr) ) { 66 | offset = offset *10 + *ptr-'0'; 67 | ptr++; 68 | } 69 | } 70 | else { 71 | while ( IS_HEX_CHAR(*ptr) ) { 72 | offset = offset * 0x10 + ( (unsigned)(*ptr-'0')<10 ? (*ptr-'0') : 0xA + ((*ptr-'A'<6) ? (*ptr-'A') : (*ptr-'a') )); 73 | ptr++; 74 | } 75 | } 76 | 77 | // If line doesn't include proper separator, skip this line ... 78 | if ( *ptr++ != labelSeparator ) { 79 | IO::Log(IO::debug, "Failed to parse line %d, skipping", lineNum); 80 | continue; 81 | } 82 | SKIP_SPACES(ptr); 83 | 84 | // Fetch label ... 85 | char* sLabel = (char*)ptr; 86 | while ( !(*ptr == '\t' || *ptr == ' ') && *ptr ) { 87 | ptr++; 88 | } 89 | *ptr = 0x00; 90 | 91 | symbolTable.add(offset, sLabel); 92 | } 93 | 94 | #undef IS_HEX_CHAR 95 | #undef IS_NUMERIC 96 | #undef SKIP_SPACES 97 | } 98 | }; 99 | -------------------------------------------------------------------------------- /utils/convsym/src/input/TXT.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* ------------------------------------------------------------ * 3 | * ConvSym utility version 2.12 * 4 | * Input wrapper for TXT files * 5 | * ------------------------------------------------------------ */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | #include "InputWrapper.hpp" 19 | 20 | 21 | struct Input__TXT : public InputWrapper { 22 | 23 | Input__TXT() {} 24 | ~Input__TXT() {} 25 | 26 | void parse(SymbolTable& symbolTable, const char *fileName, const char * opts) { 27 | 28 | // Supported options: 29 | // /fmt='format-string' - C-style format string (default: '%s %X') 30 | // /offsetFirst? - specifies whether offset comes first in the input string (default is label followed by offset) 31 | 32 | // Default options 33 | std::string lineFormat = "%s %X"; 34 | bool offsetFirst = false; 35 | 36 | const std::map 37 | OptsList { 38 | { "fmt", { .type = OptsParser::record::p_string, .target = &lineFormat } }, 39 | { "offsetFirst", { .type = OptsParser::record::p_bool, .target = &offsetFirst } }, 40 | }; 41 | OptsParser::parse(opts, OptsList); 42 | 43 | // Setup buffer, symbols list and file for input 44 | const int sBufferSize = 1024; 45 | uint8_t sBuffer[sBufferSize]; 46 | std::multimap SymbolMap; 47 | IO::FileInput input = IO::FileInput(fileName, IO::text); 48 | if (!input.good()) { 49 | throw "Couldn't open input file"; 50 | } 51 | 52 | auto numSpecifiers = std::ranges::count(lineFormat, '%'); 53 | if (numSpecifiers < 2) { 54 | IO::Log(IO::warning, "Line format string likely has too few arguments (try '%%s %%X')"); 55 | } 56 | 57 | int lineNum = 0; 58 | const auto lineFormat_cstr = lineFormat.c_str(); 59 | while (input.readLine(sBuffer, sBufferSize) >= 0) { 60 | lineNum++; 61 | 62 | uint32_t offset = 0; 63 | char sLabel[512]; 64 | 65 | const auto result = offsetFirst 66 | ? sscanf((const char*)sBuffer, lineFormat_cstr, &offset, sLabel) 67 | : sscanf((const char*)sBuffer, lineFormat_cstr, sLabel, &offset); 68 | if (result != 2) { 69 | IO::Log(IO::debug, "Failed to parse line %d, skipping (result=%d)", lineNum, result); 70 | continue; 71 | } 72 | 73 | symbolTable.add(offset, sLabel); 74 | } 75 | } 76 | 77 | }; 78 | -------------------------------------------------------------------------------- /utils/convsym/src/input/Wrappers.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* ------------------------------------------------------------ * 3 | * ConvSym utility version 2.12 * 4 | * Input formats base controller * 5 | * ------------------------------------------------------------ */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include "InputWrapper.hpp" 16 | 17 | #include "ASM68K_Listing.cpp" 18 | #include "ASM68K_Sym.cpp" 19 | #include "AS_Listing.cpp" 20 | #include "AS_Listing_Experimental.cpp" 21 | #include "Log.cpp" 22 | #include "TXT.cpp" 23 | 24 | 25 | /* Input wrappers map */ 26 | std::unique_ptr getInputWrapper(const std::string& name) { 27 | static const std::map()> > 28 | wrapperTable { 29 | { "asm68k_sym", []() { return std::unique_ptr(new Input__ASM68K_Sym()); } }, 30 | { "asm68k_lst", []() { return std::unique_ptr(new Input__ASM68K_Listing()); } }, 31 | { "as_lst", []() { return std::unique_ptr(new Input__AS_Listing()); } }, 32 | { "as_lst_exp", []() { return std::unique_ptr(new Input__AS_Listing_Experimental()); } }, 33 | { "log", []() { return std::unique_ptr(new Input__Log()); } }, 34 | { "txt", []() { return std::unique_ptr(new Input__TXT()); } } 35 | }; 36 | 37 | auto entry = wrapperTable.find(name); 38 | if (entry == wrapperTable.end()) { 39 | IO::Log(IO::fatal, "Unknown input format specifier: %s", name.c_str()); 40 | throw "Bad input format specifier"; 41 | } 42 | 43 | return (entry->second)(); 44 | } 45 | -------------------------------------------------------------------------------- /utils/convsym/src/output/ASM.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* ------------------------------------------------------------ * 3 | * ConvSym utility version 2.12 * 4 | * Output wrapper for assembly file with equates * 5 | * ------------------------------------------------------------ */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include "OptsParser.hpp" 15 | #include "OutputWrapper.hpp" 16 | 17 | 18 | struct Output__Asm : public OutputWrapper { 19 | 20 | Output__Asm() {}; 21 | ~Output__Asm() {}; 22 | 23 | /** 24 | * Main function that generates the output 25 | */ 26 | void parse( 27 | std::multimap& SymbolList, 28 | const char * fileName, 29 | uint32_t appendOffset = 0, 30 | uint32_t pointerOffset = 0, 31 | const char * opts = "", 32 | bool alignOnAppend = true 33 | ) { 34 | if (appendOffset || pointerOffset || !alignOnAppend) { 35 | IO::Log(IO::warning, "Append options aren't supported by the \"asm\" output parser."); 36 | } 37 | 38 | // Supported options: 39 | // /fmt='format-string' - overrides C-style format string (default: '%X: %s') 40 | 41 | // Default options 42 | std::string lineFormat = "%s:\tequ\t$%X"; 43 | 44 | if (*opts && opts[0] == '/') { 45 | const std::map 46 | OptsList { 47 | { "fmt", { .type = OptsParser::record::p_string, .target = &lineFormat } } 48 | }; 49 | OptsParser::parse(opts, OptsList); 50 | } 51 | else if (*opts) { 52 | lineFormat = opts; 53 | } 54 | auto numSpecifiers = std::ranges::count(lineFormat, '%'); 55 | if (numSpecifiers < 2) { 56 | IO::Log(IO::warning, "Line format string likely has too few arguments (try '%%s:\tequ\t$%%X')"); 57 | } 58 | 59 | IO::FileOutput output = IO::FileOutput(fileName, IO::text); 60 | if (!output.good()) { 61 | IO::Log(IO::fatal, "Couldn't open file \"%s\"", fileName); 62 | throw "IO error"; 63 | } 64 | 65 | const auto lineFormat_cstr = lineFormat.c_str(); 66 | for (auto& symbol : SymbolList) { 67 | output.writeLine(lineFormat_cstr, symbol.second.c_str(), symbol.first); 68 | } 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /utils/convsym/src/output/Log.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* ------------------------------------------------------------ * 3 | * ConvSym utility version 2.12 * 4 | * Output wrapper for simple symbol logging * 5 | * ------------------------------------------------------------ */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include "OptsParser.hpp" 15 | #include "OutputWrapper.hpp" 16 | 17 | 18 | struct Output__Log : public OutputWrapper { 19 | 20 | Output__Log() {}; 21 | ~Output__Log() {}; 22 | 23 | /** 24 | * Main function that generates the output 25 | */ 26 | void parse( 27 | std::multimap& SymbolList, 28 | const char * fileName, 29 | uint32_t appendOffset = 0, 30 | uint32_t pointerOffset = 0, 31 | const char * opts = "", 32 | bool alignOnAppend = true 33 | ) { 34 | if (appendOffset || pointerOffset || !alignOnAppend) { 35 | IO::Log(IO::warning, "Append options aren't supported by the \"log\" output parser."); 36 | } 37 | 38 | // Supported options: 39 | // /fmt='format-string' - overrides C-style format string (default: '%X: %s') 40 | 41 | // Default options 42 | std::string lineFormat = "%X: %s"; 43 | 44 | if (*opts && opts[0] == '/') { 45 | const std::map 46 | OptsList { 47 | { "fmt", { .type = OptsParser::record::p_string, .target = &lineFormat } } 48 | }; 49 | OptsParser::parse(opts, OptsList); 50 | } 51 | else if (*opts) { 52 | lineFormat = opts; 53 | } 54 | auto numSpecifiers = std::ranges::count(lineFormat, '%'); 55 | if (numSpecifiers < 2) { 56 | IO::Log(IO::warning, "Line format string likely has too few arguments (try '%%X: %%s')"); 57 | } 58 | 59 | IO::FileOutput output = IO::FileOutput(fileName, IO::text); 60 | if (!output.good()) { 61 | IO::Log(IO::fatal, "Couldn't open file \"%s\"", fileName); 62 | throw "IO error"; 63 | } 64 | 65 | for (auto & symbol : SymbolList ) { 66 | output.writeLine(lineFormat.c_str(), symbol.first, symbol.second.c_str()); 67 | } 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /utils/convsym/src/output/OutputWrapper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | 11 | /* Base class for the output formats handlers */ 12 | struct OutputWrapper { 13 | 14 | OutputWrapper() { } 15 | virtual ~OutputWrapper() { } 16 | 17 | // Function to setup output 18 | static std::unique_ptr setupOutput( const char * fileName, int32_t appendOffset, int32_t pointerOffset, bool alignOnAppend ) { 19 | 20 | // If append offset was specified, don't overwrite the contents of file 21 | if ( appendOffset != 0 ) { 22 | std::unique_ptr output = std::make_unique(fileName, IO::append); 23 | 24 | // Make sure IO operation was successful 25 | if (!output->good()) { 26 | IO::Log(IO::fatal, "Couldn't open file \"%s\"", fileName); 27 | throw "IO error"; 28 | } 29 | 30 | // If append mode is specified: append to the end of file 31 | if ( appendOffset == -1 ) { 32 | output->setOffset( 0, IO::end ); // move pointer to the end of file ... 33 | appendOffset = output->getCurrentOffset(); 34 | 35 | // If align on append option is on (default), make sure to pad `appendOffset` if it's not even 36 | if (alignOnAppend && ((appendOffset & 1) != 0)) { 37 | IO::Log(IO::debug, "Auto-aligning append offset."); 38 | output->writeByte(0); 39 | appendOffset++; 40 | } 41 | } 42 | else { 43 | if (alignOnAppend && ((appendOffset & 1) != 0)) { 44 | IO::Log(IO::warning, "An odd append offset is specified; the offset wasn't auto-aligned."); 45 | } 46 | 47 | output->setOffset( appendOffset ); // move pointer to the specified append offset 48 | } 49 | 50 | // If pointer offset is specified 51 | if ( pointerOffset != 0 ) { 52 | output->setOffset( pointerOffset ); 53 | output->writeBELong( appendOffset ); 54 | } 55 | 56 | // Treat "appendOffset" as the base offset from now on ... 57 | output->setBaseOffset( appendOffset ); 58 | output->setOffset( 0 ); // move to the start of appending section ... 59 | 60 | return output; 61 | } 62 | 63 | // Otherwise, discard file contents if exists 64 | else { 65 | return std::make_unique(fileName); 66 | } 67 | 68 | } 69 | 70 | // Virtual function interface that handles generating output data 71 | virtual void parse( 72 | std::multimap& SymbolMap, 73 | const char * fileName, 74 | uint32_t appendOffset = 0, 75 | uint32_t pointerOffset = 0, 76 | const char * opts = "", 77 | bool alignOnAppend = true 78 | ) = 0; 79 | 80 | }; 81 | -------------------------------------------------------------------------------- /utils/convsym/src/output/Wrappers.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* ------------------------------------------------------------ * 3 | * ConvSym utility version 2.12 * 4 | * Output formats base controller * 5 | * ------------------------------------------------------------ */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include "OutputWrapper.hpp" 18 | 19 | #include "DEB1.cpp" 20 | #include "DEB2.cpp" 21 | #include "Log.cpp" 22 | #include "ASM.cpp" 23 | 24 | 25 | /* Input wrappers map */ 26 | std::unique_ptr getOutputWrapper( const std::string& name ) { 27 | static const std::map()>> 28 | wrapperTable { 29 | { "deb1", []() { return std::unique_ptr(new Output__Deb1()); } }, 30 | { "deb2", []() { return std::unique_ptr(new Output__Deb2()); } }, 31 | { "log", []() { return std::unique_ptr(new Output__Log()); } }, 32 | { "asm", []() { return std::unique_ptr(new Output__Asm()); } } 33 | }; 34 | 35 | auto entry = wrapperTable.find(name); 36 | if (entry == wrapperTable.end()) { 37 | IO::Log(IO::fatal, "Unknown output format specifier: %s", name.c_str()); 38 | throw "Bad output format specifier"; 39 | } 40 | 41 | return (entry->second)(); 42 | } 43 | -------------------------------------------------------------------------------- /utils/convsym/src/util/SymbolTable.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "IO.hpp" 11 | 12 | struct OffsetConversionOptions { 13 | uint32_t baseOffset; 14 | uint32_t offsetLeftBoundary; 15 | uint32_t offsetRightBoundary; 16 | uint32_t offsetMask; 17 | }; 18 | 19 | typedef std::unordered_map> SymbolToOffsetResolveTable; 20 | 21 | struct SymbolTable { 22 | const OffsetConversionOptions& offsetConversionOpts; 23 | const SymbolToOffsetResolveTable& symbolToOffsetResolveTable; 24 | 25 | std::multimap symbols; 26 | 27 | SymbolTable( 28 | const OffsetConversionOptions& _offsetConversionOpts, 29 | const SymbolToOffsetResolveTable& _symbolToOffsetResolveTable 30 | ): offsetConversionOpts(_offsetConversionOpts), symbolToOffsetResolveTable(_symbolToOffsetResolveTable), symbols({}) {} 31 | 32 | SymbolTable(const SymbolTable& symbolTable) = delete; 33 | SymbolTable& operator=(const SymbolTable& symbolTable) = delete; 34 | 35 | template 36 | inline bool add(uint32_t offset, LabelType label) { 37 | const uint32_t correctedOffset = (offset - offsetConversionOpts.baseOffset) & offsetConversionOpts.offsetMask; 38 | 39 | /* If we have symbols to resolve for some options (e.g. `-ref sym:MySymbolName`), resolve to offset if name matches */ 40 | if (!symbolToOffsetResolveTable.empty()) { 41 | const auto symbolToOffsetEntry = symbolToOffsetResolveTable.find(label); 42 | if (symbolToOffsetEntry != symbolToOffsetResolveTable.end()) { 43 | symbolToOffsetEntry->second.get() = correctedOffset; 44 | IO::Log(IO::debug, "Resolved requested symbol offset: %X", correctedOffset); 45 | } 46 | } 47 | 48 | if (!( 49 | correctedOffset >= offsetConversionOpts.offsetLeftBoundary && 50 | correctedOffset <= offsetConversionOpts.offsetRightBoundary 51 | )) { 52 | return false; // symbol is not inserted when offset is out of range 53 | } 54 | 55 | if (IO::LogLevel <= IO::debug) { 56 | IO::Log(IO::debug, "Adding symbol: %s", std::string(label).c_str()); 57 | } 58 | symbols.emplace(correctedOffset, std::string(label)); 59 | 60 | return true; 61 | } 62 | 63 | }; 64 | -------------------------------------------------------------------------------- /utils/convsym/tests/.gitignore: -------------------------------------------------------------------------------- 1 | input 2 | output-expected 3 | output/* 4 | !output/.gitkeep -------------------------------------------------------------------------------- /utils/convsym/tests/input.tar.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/utils/convsym/tests/input.tar.xz -------------------------------------------------------------------------------- /utils/convsym/tests/output-expected.tar.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/utils/convsym/tests/output-expected.tar.xz -------------------------------------------------------------------------------- /utils/convsym/tests/output/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladikcomper/md-modules/84a9a3d50a53f628d2aab91fc851ba00b6a48337/utils/convsym/tests/output/.gitkeep -------------------------------------------------------------------------------- /utils/lib-py/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ -------------------------------------------------------------------------------- /utils/lib-py/test_framework.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from typing import cast, Union, List, Literal, NamedTuple 3 | from dataclasses import dataclass 4 | from pathlib import Path 5 | import platform 6 | import filecmp 7 | import tarfile 8 | 9 | CompareResult = Union['tuple[Literal[True], None]', 'tuple[Literal[False], str]'] 10 | 11 | class DataSource: 12 | def read(self) -> bytes: raise Exception('Not implemented') 13 | def write(self, data: bytes): raise Exception('Not implemented') 14 | def cmp(self, other, textMode=False) -> CompareResult: 15 | data1, data2 = self.read(), cast(DataSource, other).read() 16 | 17 | # For Windows and MacOS in text mode, we have to convert newlines, since all examples store Linux newlines 18 | if textMode and platform.system() in ('Windows', 'Darwin'): 19 | data1 = data1.replace(b'\r\n', b'\n').replace(b'\r', b'\n') 20 | data2 = data2.replace(b'\r\n', b'\n').replace(b'\r', b'\n') 21 | 22 | if data1 != data2: 23 | return (False, f'Expected "{data2}", got "{data1}"') if len(data1) + len(data2) < 512 else (False, "Expected data to match") 24 | return (True, None) 25 | 26 | @dataclass 27 | class File(DataSource): 28 | path: str 29 | def read(self) -> bytes: return self.getPath().read_bytes() 30 | def write(self, data: bytes): return self.getPath().write_bytes(data) 31 | def getPath(self) -> Path: return Path(getBaseDir(), self.path) 32 | def cmp(self, other, textMode=False) -> CompareResult: 33 | # If both data sources to compare are files, we can use `filecmp.cmp` as an optimization. 34 | # But this is only applicable in non-text mode OR with text mode on Linux, since all example use Linux newlines 35 | if isinstance(other, File) and ((not textMode) or (textMode and platform.system() == 'Linux')): 36 | return (True, None) if filecmp.cmp(self.getPath(), other.getPath()) else (False, f'Expected "{self.getPath()}" to match "{other.getPath()}"') 37 | return super().cmp(other, textMode) 38 | 39 | @dataclass 40 | class Buffer(DataSource): 41 | value: bytes = bytes() 42 | def read(self) -> bytes: return self.value 43 | def write(self, data: bytes): self.value = data 44 | 45 | @dataclass 46 | class Void(DataSource): 47 | def read(self) -> bytes: return bytes() 48 | def write(self, data: bytes): pass 49 | 50 | 51 | CommandResult = Union['tuple[Literal[True], DataSource]', 'tuple[Literal[False], str]'] 52 | 53 | @dataclass() 54 | class Command: 55 | output: Union[DataSource, None] = None 56 | input: Union[DataSource, None] = None 57 | 58 | def execute(self, input: DataSource, output: DataSource) -> CommandResult: 59 | return (False, 'Unable to execute command: not implemented') 60 | 61 | @dataclass 62 | class CheckMatch(Command): 63 | text: bool = False 64 | 65 | def execute(self, input: DataSource, output: DataSource) -> CommandResult: 66 | success, diff_message = input.cmp(output, self.text) 67 | return (True, output) if success else (False, cast(str, diff_message)) 68 | 69 | 70 | Test = NamedTuple('Test', description=str, pipeline='tuple[Command, ...]') 71 | 72 | 73 | def unarchiveDirectory(path: str, *, skipIfExists=True): 74 | dir_path = Path(getBaseDir(), path) 75 | dir_archive_path = Path(getBaseDir(), path + '.tar.xz') 76 | if dir_path.exists() and dir_path.is_dir() and skipIfExists: 77 | return 78 | 79 | print(f"Unpacking {str(dir_archive_path)}...") 80 | with tarfile.open(dir_archive_path, 'r:xz') as f: f.extractall(getBaseDir()) 81 | 82 | 83 | def archiveDirectory(path: str): 84 | dir_path = Path(getBaseDir(), path) 85 | dir_archive_path = Path(getBaseDir(), path + '.tar.xz') 86 | 87 | if not dir_path.exists(): 88 | raise Exception(f'Directory {path} wasn\'t unpacked.') 89 | if dir_archive_path.exists(): 90 | dir_archive_path.unlink() 91 | 92 | print(f"Archiving {str(dir_path)} -> {str(dir_archive_path)}") 93 | with tarfile.open(dir_archive_path, 'x:xz', preset=9) as f: 94 | f.add(dir_path, arcname=path) 95 | 96 | 97 | def getUsedFilesList(tests: 'tuple[Test, ...]') -> List[str]: 98 | file_sources = [ 99 | source 100 | for test in tests for command in test.pipeline for source in (command.input, command.output) 101 | if isinstance(source, File) 102 | ] 103 | file_paths = [ str(source.getPath()) for source in file_sources ] 104 | return file_paths 105 | 106 | 107 | def runTests(tests: 'tuple[Test, ...]') -> bool: 108 | has_failed_tests = False 109 | 110 | for test_id, test in enumerate(tests): 111 | print(f'[Test {test_id:d}] {test.description} ... ', flush=True, end='') 112 | 113 | pipeline_result: CommandResult = (True, Buffer()) 114 | 115 | for command in test.pipeline: 116 | try: 117 | input, output = command.input or pipeline_result[1], command.output or Buffer() 118 | pipeline_result = command.execute(input, output) 119 | if pipeline_result[0] == False: break 120 | 121 | except Exception as e: 122 | pipeline_result = (False, str(e)); break 123 | 124 | success, value = pipeline_result 125 | if success: 126 | print("OK") 127 | else: 128 | print(f'FAILED: {value}') 129 | has_failed_tests = True 130 | 131 | return not has_failed_tests 132 | 133 | _BASE_DIR_: str = '.' 134 | 135 | def setBaseDir(base_dir: str): 136 | global _BASE_DIR_ 137 | _BASE_DIR_ = base_dir 138 | 139 | 140 | def getBaseDir() -> str: 141 | global _BASE_DIR_ 142 | return _BASE_DIR_ 143 | -------------------------------------------------------------------------------- /utils/lib/ArgvParser.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* ------------------------------------------------------------ * 3 | * Debugging Modules Utilities Core * 4 | * Argument values parser helper * 5 | * (c) 2017-2018, Vladikcomper * 6 | * ------------------------------------------------------------ */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "IO.hpp" 16 | 17 | 18 | namespace ArgvParser { 19 | 20 | /* Structure that handles parameter definitions */ 21 | struct record { 22 | enum { flag, hexNumber, hexRange, string, string_list, custom } type; 23 | void * target; 24 | void * target2; 25 | }; 26 | 27 | /** 28 | * Function to parse command line arguments (argv, argc) according to parameter data structures 29 | */ 30 | inline void parse(const char ** argv, int argc, std::map ParametersList) { 31 | 32 | for (int i=0; ifirst.c_str()); \ 37 | break; \ 38 | } 39 | 40 | if (parameter != ParametersList.end()) { 41 | switch (parameter->second.type) { 42 | case record::flag: 43 | *((bool*)parameter->second.target) = true; 44 | break; 45 | 46 | case record::hexNumber: 47 | _GET_NEXT_ARGUMENT 48 | *(unsigned int*)parameter->second.target = std::stoul(argv[i], 0, 16); 49 | break; 50 | 51 | case record::hexRange: 52 | _GET_NEXT_ARGUMENT 53 | *(unsigned int*)parameter->second.target = std::stoul(argv[i], 0, 16); 54 | _GET_NEXT_ARGUMENT 55 | *(unsigned int*)parameter->second.target2 = std::stoul(argv[i], 0, 16); 56 | break; 57 | 58 | case record::string: 59 | _GET_NEXT_ARGUMENT 60 | *((std::string*)parameter->second.target) = argv[i]; 61 | break; 62 | 63 | case record::string_list: 64 | _GET_NEXT_ARGUMENT 65 | ((std::vector*)parameter->second.target)->emplace_back(argv[i]); 66 | break; 67 | 68 | case record::custom: 69 | _GET_NEXT_ARGUMENT 70 | (*(std::function*)parameter->second.target2)(argv[i], parameter->second.target); 71 | 72 | default: 73 | throw "Incorrect or broken parameters data"; 74 | } 75 | } 76 | else { 77 | IO::Log(IO::warning, "Unknown parameter \"%s\" passed. Parameter is ignored", argv[i]); 78 | } 79 | 80 | #undef _GET_NEXT_ARGUMENT 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /utils/lib/BitStream.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* ------------------------------------------------------------ * 3 | * Debugging Modules Utilities Core * 4 | * Bitstream helper class * 5 | * (c) 2017-2018, 2024 Vladikcomper * 6 | * ------------------------------------------------------------ */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | class BitStream { 16 | 17 | std::vector buffer; 18 | uint32_t currentBytePos; 19 | uint8_t currentBitPos; 20 | 21 | public: 22 | 23 | BitStream(): buffer(), currentBytePos(0), currentBitPos(8) { // Constructor 24 | buffer.push_back(0x00); 25 | }; 26 | 27 | /** 28 | * Returns current offset within the stream 29 | */ 30 | uint32_t getCurrentPos() { 31 | return currentBytePos; 32 | }; 33 | 34 | /** 35 | * Subroutine to flush the buffer 36 | */ 37 | void flush() { 38 | buffer.push_back(0x00); 39 | currentBitPos = 8; 40 | currentBytePos++; 41 | }; 42 | 43 | /** 44 | * Subroutine to push a bit 45 | */ 46 | void pushBit( unsigned int bit ) { 47 | if (currentBitPos == 0) { // if the current byte is fully packed, flush it 48 | flush(); 49 | } 50 | buffer[currentBytePos] |= (bit << --currentBitPos); 51 | }; 52 | 53 | /** 54 | * Pushes a code containing a given number of bits 55 | */ 56 | void pushCode(uint32_t code, uint8_t codeLength) { 57 | if (currentBitPos == 0) { // if the current byte is fully packed, flush it 58 | flush(); 59 | } 60 | 61 | const uint8_t remainingBits = std::max(codeLength - currentBitPos, 0); 62 | buffer[currentBytePos] |= (code>>remainingBits) << (currentBitPos -= std::min(currentBitPos, codeLength)); 63 | 64 | if (remainingBits > 0) { 65 | pushCode(code & ((1< 11 | #include 12 | 13 | #include "IO.hpp" 14 | 15 | namespace OptsParser { 16 | 17 | 18 | /* Structure that handles option definition */ 19 | struct record { 20 | enum { p_bool, p_char, p_string } type; 21 | void * target; 22 | }; 23 | 24 | /** 25 | * Function to parse string consisting of options and their values 26 | */ 27 | void parse(const char* opts, std::map OptsList) { 28 | 29 | const char * ptr = opts; 30 | bool illegalCharactersFound = false; 31 | 32 | #define IS_VALID_OPERATOR(X) (X=='='||X=='+'||X=='-') 33 | 34 | while ( *ptr ) { 35 | 36 | // Fetch option start token 37 | if ( *ptr == '/' ) { 38 | 39 | const char *ptr_start, *ptr_end; 40 | 41 | // Fetch option name 42 | ptr_start = ++ptr; 43 | while ( !IS_VALID_OPERATOR(*ptr) && *ptr ) ptr++; 44 | std::string strOptionName( ptr_start, ptr-ptr_start ); 45 | 46 | // Fetch option operator 47 | char cOptionOperator = *ptr; 48 | 49 | // Fetch option value 50 | ptr_start = ++ptr; 51 | if ( *ptr == '\'' ) { // if quote fetched, seek for closing quote, capture string inbetween 52 | ptr_start = ++ptr; // correct string pointer to pass the quote itself ... 53 | while ( *ptr!='\'' && *ptr ) ptr++; 54 | ptr_end = ptr; 55 | if ( !*ptr ) { // if string ended before fetching a closing quote, issue error 56 | IO::Log( IO::warning, "Missed closing quote for value of parameter %s", strOptionName.c_str() ); 57 | } 58 | else { // otherwise, skip the quote 59 | ptr++; 60 | } 61 | } 62 | else { // otherwise, seek for space 63 | while ( *ptr!=' ' && *ptr ) ptr++; 64 | ptr_end = ptr; 65 | } 66 | std::string strOptionValue( ptr_start, ptr_end-ptr_start ); 67 | 68 | // Decode option according to the options list 69 | auto option = OptsList.find(strOptionName); 70 | if ( option != OptsList.end() ) { 71 | switch ( option->second.type ) { 72 | 73 | case record::p_bool: 74 | *((bool*)option->second.target) = (cOptionOperator == '+'); 75 | break; 76 | 77 | case record::p_char: 78 | *((char*)option->second.target) = strOptionValue[0]; 79 | break; 80 | 81 | case record::p_string: 82 | *((std::string*)option->second.target) = strOptionValue; 83 | break; 84 | 85 | default: 86 | throw "Incorrect or broken option list entry"; 87 | 88 | } 89 | } 90 | else { 91 | IO::Log( IO::error, "Unknown option: /%s, skipping", strOptionName.c_str() ); 92 | } 93 | } 94 | 95 | // ... otherwise 96 | else { 97 | 98 | // If character is not a whitespace, issue warning once 99 | if ( *ptr!=' ' ) { 100 | if ( illegalCharactersFound == false ) { 101 | illegalCharactersFound = true; 102 | IO::Log( IO::warning, "Illegal characters found while parsing option string" ); 103 | } 104 | } 105 | 106 | // Skip character 107 | ptr++; 108 | } 109 | } 110 | 111 | } 112 | 113 | } -------------------------------------------------------------------------------- /utils/lib/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | inline uint16_t swap16(uint16_t x) { return (x>>8)|(x<<8); } 6 | inline uint32_t swap32(uint32_t x) { return (x>>24)|((x>>8)&0xFF00)|((x<<8)&0xFF0000)|(x<<24); } 7 | --------------------------------------------------------------------------------