Skip to content

Latest commit

 

History

History
433 lines (334 loc) · 18.9 KB

File metadata and controls

433 lines (334 loc) · 18.9 KB

Installing MD Debugger in Sonic 2 GitHub Disassembly

This guide describes a step-by-step installation process of the MD Error Handler and Debugger 2.5 and above in Sonic 2 GitHub Disassembly.

The base disassembly used for this installation is available here: https://github.com/sonicretro/s2disasm

Step 1. Download and unpack the debugger

  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
  2. Download errorhandler-2.6.zip from this page, find as directory inside;
  3. Extract files from the as directory (AS bundle) into disassembly's root directory.

Step 2. Include debugger macros in your disassembly

Open s2.asm in your favorite text editor and paste the following right above StartOfRom::

	include	"Debugger.asm"

Don't try to build the ROM just yet. We're still missing one important component: the Error Handler itself!

Step 3. Install the Error Handler

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).

Note

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.

  1. At the very bottom of s2.asm, find ; end of 'ROM', and just above it, add the following snippet:

    ; ==============================================================
    ; --------------------------------------------------------------
    ; Debugging modules
    ; --------------------------------------------------------------
    
       include   "ErrorHandler.asm"
    
    ; --------------------------------------------------------------
    ; WARNING!
    ;	DO NOT put any data from now on! DO NOT use ROM padding!
    ;	Symbol data should be appended here after ROM is compiled
    ;	by ConvSym utility, otherwise debugger modules won't be able
    ;	to resolve symbol names.
    ; --------------------------------------------------------------

    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).

    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.

  2. Now we need to actually attach point exceptions in the ROM's vector table to the error handler. To do so, in s2.asm find Vectors: and replace everything from the line Vectors: through ; byte_100: with the following snippet:

    Vectors:
    	dc.l System_Stack	; Initial stack pointer value
    	dc.l EntryPoint		; Start of program
    	dc.l BusError		; Bus error
    	dc.l AddressError	; Address error (4)
    	dc.l IllegalInstr	; Illegal instruction
    	dc.l ZeroDivide		; Division by zero
    	dc.l ChkInstr		; CHK exception
    	dc.l TrapvInstr		; TRAPV exception (8)
    	dc.l PrivilegeViol	; Privilege violation
    	dc.l Trace			; TRACE exception
    	dc.l Line1010Emu	; Line-A emulator
    	dc.l Line1111Emu	; Line-F emulator (12)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved) (16)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved) (20)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved) (24)
    	dc.l ErrorExcept	; Spurious exception
    	dc.l ErrorExcept	; IRQ level 1
    	dc.l ErrorExcept	; IRQ level 2
    	dc.l ErrorExcept	; IRQ level 3 (28)
    	dc.l H_Int			; IRQ level 4 (horizontal retrace interrupt)
    	dc.l ErrorExcept	; IRQ level 5
    	dc.l V_Int			; IRQ level 6 (vertical retrace interrupt)
    	dc.l ErrorExcept	; IRQ level 7 (32)
    	dc.l ErrorExcept	; TRAP #00 exception
    	dc.l ErrorExcept	; TRAP #01 exception
    	dc.l ErrorExcept	; TRAP #02 exception
    	dc.l ErrorExcept	; TRAP #03 exception (36)
    	dc.l ErrorExcept	; TRAP #04 exception
    	dc.l ErrorExcept	; TRAP #05 exception
    	dc.l ErrorExcept	; TRAP #06 exception
    	dc.l ErrorExcept	; TRAP #07 exception (40)
    	dc.l ErrorExcept	; TRAP #08 exception
    	dc.l ErrorExcept	; TRAP #09 exception
    	dc.l ErrorExcept	; TRAP #10 exception
    	dc.l ErrorExcept	; TRAP #11 exception (44)
    	dc.l ErrorExcept	; TRAP #12 exception
    	dc.l ErrorExcept	; TRAP #13 exception
    	dc.l ErrorExcept	; TRAP #14 exception
    	dc.l ErrorExcept	; TRAP #15 exception (48)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved) (52)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved) (56)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved) (60)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved)
    	dc.l ErrorExcept	; Unused (reserved) (64)
    ; byte_100:
  3. Now run build.lua (or build.bat on Windows if Lua isn't globally available in your environment) and make sure your ROM builds properly.

Once everything's done, congratulations, the Error Handler is installed, you're almost there!

Step 4. Install ConvSym to generate debug symbols

Note

This step involves changing Lua build system, which can get updated every now and then. If that happens, code changes may differ for you. If you find out that this guide is not up-to-date with the disassembly, please open an issue in this repository or create a pull request with the update.

  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

  2. Download the ConvSym utility (convsym-2.12.1.zip) for your platform: Windows or Linux (FreeBSD, MacOS and others are also supported, but you need to build from the source code);

  3. Extract the ConvSym executable to the correct path in your disassembly depending on your platform:

    • build_tools\Windows-x86\convsym.exe for Windows;
    • build_tools/Linux-x86_64/convsym for Linux (64-bit);
    • build_tools/BSD-x86_64/convsym for FreeBSD;
    • build_tools/Mac-x86_64/convsym for MacOS;
  4. To properly append debug symbols, you need to disable automatic ROM padding first.

    Open s2.asm and search for padToPowerOfTwo =. You should see the following fragment:

    ;	| If 0, a REV00 ROM is built
    ;	| If 1, a REV01 ROM is built, which contains some fixes
    ;	| If 2, a (probable) REV02 ROM is built, which contains even more fixes
    padToPowerOfTwo = 1

    Change padToPowerOfTwo = 1 to padToPowerOfTwo = 0.

  5. Open build.lua and locate the following lines:

    message_abort_wrapper(common.build_rom("s2", "s2built", "", "-p=0 -z=0," .. (improved_sound_driver_compression and "saxman-optimised" or "saxman-bugged") .. ",Size_of_Snd_driver_guess,after", true, repository))
    
    -- Correct the compressed sound driver size, which we couldn't do until p2bin had been ran.
    local comp_z80_size, movewZ80CompSize
    
    for line in io.lines("s2.h") do
    	local match_begin, match_end = string.find(line, "comp_z80_size")
    
    	if match_begin ~= nil then
    		comp_z80_size = tonumber(line:match("0x%x+", match_end))
    	end
    
    	local match_begin, match_end = string.find(line, "movewZ80CompSize")
    
    	if match_begin ~= nil then
    		movewZ80CompSize = tonumber(line:match("0x%x+", match_end))
    	end
    end
    
    if comp_z80_size ~= nil and movewZ80CompSize ~= nil then
    	local rom = io.open("s2built.bin", "r+b")
    
    	rom:seek("set", movewZ80CompSize + 2)
    	rom:write(string.pack(">I2", comp_z80_size))
    
    	rom:close()
    end

    We want to make the code above reusable, so replace it with this:

    -- Correct the compressed sound driver size, which we couldn't do until p2bin had been ran.
    local function fixZ80CompSize(rompath)
    	local comp_z80_size, movewZ80CompSize
    
    	for line in io.lines("s2.h") do
    		local match_begin, match_end = string.find(line, "comp_z80_size")
    
    		if match_begin ~= nil then
    			comp_z80_size = tonumber(line:match("0x%x+", match_end))
    		end
    
    		local match_begin, match_end = string.find(line, "movewZ80CompSize")
    
    		if match_begin ~= nil then
    			movewZ80CompSize = tonumber(line:match("0x%x+", match_end))
    		end
    	end
    
    	if comp_z80_size ~= nil and movewZ80CompSize ~= nil then
    		local rom = io.open(rompath, "r+b")
    
    		rom:seek("set", movewZ80CompSize + 2)
    		rom:write(string.pack(">I2", comp_z80_size))
    
    		rom:close()
    	end
    end
    
    message_abort_wrapper(common.build_rom("s2", "s2built", "", "-p=0 -z=0," .. (improved_sound_driver_compression and "saxman-optimised" or "saxman-bugged") .. ",Size_of_Snd_driver_guess,after", true, repository))
    fixZ80CompSize("s2built.bin")

    We basically wrapped it into local function ... end block and replaced "s2built.bin" with rompath parameter. We also moved common.build_rom call below it and call our new fixZ80CompSize function specifying our ROM name dynamically.

  6. Finally, just after the code you modified above, add extra calls to build and fix a DEBUG ROM:

    -- Create DEBUG build
    message_abort_wrapper(common.build_rom("s2", "s2built.debug", "-D __DEBUG__ -OLIST s2.debug.lst", "-p=0 -z=0," .. (improved_sound_driver_compression and "saxman-optimised" or "saxman-bugged") .. ",Size_of_Snd_driver_guess,after", true, repository))
    fixZ80CompSize("s2built.debug.bin")
  7. Now, find os.remove("s2.h") line just below and right after it, insert this fragment:

    -- Append debug symbols to ROMs using ConvSym
    local extra_tools = common.find_tools("debug symbol generator", "https://github.com/vladikcomper/md-modules", repository, "convsym")
    if extra_tools == nil then
    	common.show_flashy_message("Build failed. See above for more details.")
    	os.exit(false)
    end
    os.execute(extra_tools.convsym .. " s2.lst s2built.bin -input as_lst -range 0 FFFFFF -a")
    os.execute(extra_tools.convsym .. " s2.debug.lst s2built.debug.bin -input as_lst -exclude -filter \"z[A-Z].+\" -range 0 FFFFFF -a")
  8. Finally, another few lines below you'll find common.fix_header("s2built.bin"). Right after it, also add this:

    common.fix_header("s2built.debug.bin")
Verifying that you've modified "build.lua" correctly

If you're having issues with insertions listed above or want to double-check, here's a full diff:

diff --git a/build.lua b/build.lua
index 8da7917..a6d6488 100755
--- a/build.lua
+++ b/build.lua
@@ -172,39 +172,59 @@ hashes_file:close()
 -- Huzzah: we are done with assembling and compressing the music.
 -- We can move onto building the rest of the ROM.
 
-message_abort_wrapper(common.build_rom("s2", "s2built", "", "-p=0 -z=0," .. (improved_sound_driver_compression and "saxman-optimised" or "saxman-bugged") .. ",Size_of_Snd_driver_guess,after", true, repository))
-
 -- Correct the compressed sound driver size, which we couldn't do until p2bin had been ran.
-local comp_z80_size, movewZ80CompSize
+local function fixZ80CompSize(rompath)
+	local comp_z80_size, movewZ80CompSize
+
+	for line in io.lines("s2.h") do
+		local match_begin, match_end = string.find(line, "comp_z80_size")
+
+		if match_begin ~= nil then
+			comp_z80_size = tonumber(line:match("0x%x+", match_end))
+		end
 
-for line in io.lines("s2.h") do
-	local match_begin, match_end = string.find(line, "comp_z80_size")
+		local match_begin, match_end = string.find(line, "movewZ80CompSize")
 
-	if match_begin ~= nil then
-		comp_z80_size = tonumber(line:match("0x%x+", match_end))
+		if match_begin ~= nil then
+			movewZ80CompSize = tonumber(line:match("0x%x+", match_end))
+		end
 	end
 
-	local match_begin, match_end = string.find(line, "movewZ80CompSize")
+	if comp_z80_size ~= nil and movewZ80CompSize ~= nil then
+		local rom = io.open(rompath, "r+b")
 
-	if match_begin ~= nil then
-		movewZ80CompSize = tonumber(line:match("0x%x+", match_end))
+		rom:seek("set", movewZ80CompSize + 2)
+		rom:write(string.pack(">I2", comp_z80_size))
+
+		rom:close()
 	end
 end
 
-if comp_z80_size ~= nil and movewZ80CompSize ~= nil then
-	local rom = io.open("s2built.bin", "r+b")
+message_abort_wrapper(common.build_rom("s2", "s2built", "", "-p=0 -z=0," .. (improved_sound_driver_compression and "saxman-optimised" or "saxman-bugged") .. ",Size_of_Snd_driver_guess,after", true, repository))
 
-	rom:seek("set", movewZ80CompSize + 2)
-	rom:write(string.pack(">I2", comp_z80_size))
+fixZ80CompSize("s2built.bin")
 
-	rom:close()
-end
+-- Create DEBUG build
+message_abort_wrapper(common.build_rom("s2", "s2built.debug", "-D __DEBUG__ -OLIST s2.debug.lst", "-p=0 -z=0," .. (improved_sound_driver_compression and "saxman-optimised" or "saxman-bugged") .. ",Size_of_Snd_driver_guess,after", true, repository))
+
+fixZ80CompSize("s2built.debug.bin")
 
 -- Remove the header file, since we no longer need it.
 os.remove("s2.h")
 
+-- Append debug symbols to ROMs using ConvSym
+local extra_tools = common.find_tools("debug symbol generator", "https://github.com/vladikcomper/md-modules", repository, "convsym")
+if extra_tools == nil then
+	common.show_flashy_message("Build failed. See above for more details.")
+	os.exit(false)
+end
+os.execute(extra_tools.convsym .. " s2.lst s2built.bin -input as_lst -range 0 FFFFFF -a")
+os.execute(extra_tools.convsym .. " s2.debug.lst s2built.debug.bin -input as_lst -exclude -filter \"z[A-Z].+\" -range 0 FFFFFF -a")
+
 -- Correct the ROM's header with a proper checksum and end-of-ROM value.
 common.fix_header("s2built.bin")
 
+common.fix_header("s2built.debug.bin")
+
 -- A successful build; we can quit now.
 os.exit(exit_code, false)

This will produce two builds for you: the RELEASE build (s2built.bin) and the DEBUG one (s2built.debug.bin). They should be identical for now, but if you start using some of the advanced debugger features, like assertions and KDebug interface, these features will be compiled and enabled only in DEBUG builds to avoid performance penalties when not debugging.

Note

AS compiles code in case-insensitive mode by default. This means all symbols will be converted to upper-case. If you want to preserve case, add -U to compile flags to enable the case-sensitive mode. Beware that you may need to fix a lot of labels if their casing differs!

That's it! Save build.lua and run it (or build.bat on Windows as its launcher). Make sure the are no errors in the output.

Step 5. Testing the debugger with an intentional crash (optional)

Now, let's try your freshly installed debugger in action. For testing purposes, let's make it so the game shows custom exception if you press A playing as Sonic. We then extend and customize our exception a little.

In s2.asm, find Obj01_Normal:. Right below it, add the following lines as shown:

Obj01_Normal:
	btst	#button_A, Ctrl_1_Press_Logical	; is A pressed?
	beq.s	.skip				; if not, branch
	RaiseError "Intentional crash test"	;
.skip:

Now, build your ROM, start a level and press A at any time. You should see the generic exception screen (same as you get for a normal exception, except for the header). While on this screen, you can press B to display backtrace, A to which symbols are referenced by the address registers. Press Start or C button (if unmapped) to switch to the main exception screen.

Looks beautiful, isn't it? But there's more to it.

We can display additional information about our exception if needed:

-	RaiseError "Intentional crash test"
+	RaiseError "Intentional crash test:%<endl>Level ID: %<.w Current_ZoneAndAct>"

Now, let's test a sample debugger, shall we? Create a new SampleDebugger.asm file in your disassembly's root and paste the following code to it:

SampleDebugger:
	Console.WriteLine "%<pal1>Camera (FG): %<pal0>%<.w Camera_X_pos>-%<.w Camera_Y_pos>"
	Console.WriteLine "%<pal1>Camera (BG): %<pal0>%<.w Camera_BG_X_pos>-%<.w Camera_BG_Y_pos>"
	Console.BreakLine
	
	Console.WriteLine "%<pal1>Objects IDs in slots:%<pal0>"
	Console.Write "%<setw>%<39>"       ; format slots table nicely ...

	lea 	Object_RAM, a0
	move.w 	#(LevelOnly_Object_RAM_End-Object_RAM)/object_size-1, d0
	
	.DisplayObjSlot:
	    Console.Write "%<.b (a0)> "
	    lea       next_object(a0), a0
	    dbf       d0, .DisplayObjSlot

	rts

Include your new file somewhere in s2.asm. I recommend including it right above the Error Handler (include "ErrorHandler.asm"):

    include   "SampleDebugger.asm"

Warning

Remember not to include anything after include "ErrorHandler.asm" not to break debug symbol support.

To use this debugger in RaiseError, pass its label (SampleDebugger) as the second argument:

-	RaiseError "Intentional crash test:%<endl>Level ID: %<.w Current_ZoneAndAct>"
+	RaiseError "Intentional crash test:%<endl>Level ID: %<.w Current_ZoneAndAct>", SampleDebugger

If you now try to run it, you should see a differently looking exception screen. It now displays camera coordinates and object slots.

You can also use your debugger globally and call it from any exception. To demonstrate, let's map it to the C button of a generic exception screen. Open Debugger.asm and locate these lines:

; Debuggers mapped to pressing A/B/C on the exception screen
; Use 0 to disable button, use debugger's entry point otherwise.
DEBUGGER__EXTENSIONS__BTN_A_DEBUGGER:	equ		Debugger_AddressRegisters	; display address register symbols
DEBUGGER__EXTENSIONS__BTN_B_DEBUGGER:	equ		Debugger_Backtrace			; display exception backtrace
DEBUGGER__EXTENSIONS__BTN_C_DEBUGGER:	equ		0		; disabled

Try to change the value of DEBUGGER__EXTENSIONS__BTN_C_DEBUGGER from 0 to SampleDebugger. Now pressing any the C button on any exception will call this debugger separately. Press Start to return to the main exception.

Note

You may notice that the screen contents are slightly different when SampleDebugger is called separately. This is because we don't have an exception header rendered and text itself is aligned differently when a debugger is invoked directly. If you want to align text the same way exception screen does it, you can add Console.Write "%<setx>%<1>%<setw>%<38>" at the beginning of the debugger.

When you've done playing, feel free to revert any changes and intentionally thrown exceptions from this step.