In version 23, I have created a debugger to accompany the assembler. It is a machine-level debugger with features including: breakpoints, memory watch, view listing.
The debugger is small in size, at around 10kb. The main reason behind this is that it relies on assembler-generated listing files to display source code. This means that it does not need its own decoder to translate bytes to assembler mnemonics.
Coding a debugger has been one of my most unique projects. Essentially, the debugger must "dance" around the watched program, without disturbing it. The hardest part was the design of single step/breakpoint behaviour, especially juggling breakpoint interrupt invocations and ensuring flags are set properly upon returning control back to the watched program.
For example, to support breakpoints, the debugger effectively modifies the watched program (in memory). Once the user "moves off" a breakpoint, the debugger must exert caution to restore the watched program to its original state, while still keeping breakpoint-related bytes inserted in the binary.
The debugger is invoked via its command-line wrapper DBG, or directly from within ASMIDE, while editing source code. Given the amounts of memory, stack, and flag manipulation needed by the debugger, assembly language was very suitable to write it.