TRS-80 DOS - VTOS 4.0 for the Model I - SYS9/SYS Extended Debugger Disassembled
Page Customization
Page Index
SYS9/SYS - Extended Debugger Overlay
Other Navigation
Introduction:
VTOS 4.0 SYS9/SYS Disassembly - Extended DEBUG Facility (Model I)
SYS9/SYS is the extended DEBUG facility overlay for VTOS 4.0 on the TRS-80 Model I. It is loaded into high memory when the user issues the DEBUG EXT command (setting the EXT flag). Once loaded, SYS9 augments the resident SYS5/SYS debugger with a full set of advanced commands that are not available in the standard debugger. According to the VTOS manual, the extended facility is loaded into high memory, meaning SYS9 installs itself above the current high-memory boundary at 4049H - it subtracts its own length from the boundary and copies its code there, making it permanently resident until the next cold boot.
SYS9 occupies addresses 4E00H-51F4H in its load image (the standard VTOS overlay slot). The overlay is structured around a central RST 28H dispatch table at 4E26H-4E2CH. The RST 28H SVC mechanism calls into SYS9 with a command character in Register A; SYS9 then dispatches to the appropriate handler by comparing A against a chain of command byte values. All extended commands are therefore accessible through the standard VTOS overlay call mechanism already used by SYS1-SYS8.
The extended commands provided by SYS9 are: B (Block Move - copy a block of memory), E (Enter data into memory byte by byte), F (Fill a memory range with a single byte value), J (Jump over the next instruction byte by incrementing the saved PC), L (Locate the next occurrence of a data byte in memory), N (position to the Next relative block or instruction), O (normal return to VTOS, exiting the debugger), P (Print memory in both hex and ASCII format), Q (Query an input port or send a data byte to an output port), T (Type ASCII data into memory interactively), V (Verify two memory blocks are identical), W (search memory for a Word Address), and the unnamed disk transfer command (transfer a cylinder or sector to or from disk). The P command also contains the shared hex-output subroutines used by several other extended commands.
SYS9 interacts closely with SYS5/SYS (the resident debugger), sharing the same memory work variables at 4060H, 4062H, 4063H, and 405FH. It also calls the SYS0 drive-select routine at 478FH and the disk I/O routines at 4763H, 4768H, and 4777H for the disk transfer command. The hex parameter input routines at 518AH and 51A3H (at the tail of the overlay) are shared entry points used by nearly every extended command.
Variables and Work Areas
| Address Range Size | Purpose |
|---|---|
| 4049H-404AH 2 bytes | Detected RAM top address. SYS9 subtracts 03CDH from this value and stores the result back, reducing the high-memory boundary to accommodate its own installation. Also used as the upper-address limit for the B (Block Move) command length calculation. |
| 405FH 1 byte | High byte of a 16-bit work register used by the W (Word search) and disk commands. Stores the high byte of the word being searched for (W command) or the computed sectors-per-track value (disk command). |
| 4060H-4061H 2 bytes | Current address pointer / second parameter work register. Used by B, E, F, L, N, T, V, W, and disk commands as the running destination address, the second address parameter, or the current scan pointer. |
| 4062H 1 byte | Low byte of a 16-bit work register / byte value work cell. Used by the L command as the byte to locate, by the W command as the low byte of the word to find, and by the disk command as the sector register cache and sector counter. |
| 4063H-4064H 2 bytes | Display address / first parameter work register. Used by B, V, and W commands as the first (source) address parameter, and by L and N commands as the page-aligned display address to update after the search. |
| 407BH-407CH 2 bytes | Saved program counter (resume address) in the SYS5 register save area. The J (Jump over) command increments this value by 1 to skip the next instruction byte. |
| 4310H-4311H 2 bytes | Address register A - the primary address parameter used by the B command installation sequence. Stores the source address for the block move. Updated after the B command to reflect the new high-memory top after SYS9 installs itself. |
| 5135H-5136H 2 bytes | Address register B - work cell within the SYS9 overlay's own address space. Stores the prior value of the high-memory pointer (from 4310H) before SYS9 adjusts it, used as the destination of the LDIR during self-installation. |
Major Routines
| Address | Name and Purpose |
|---|---|
| 4E00H | SYS9 Installation Entry Point Called when the user issues DEBUG EXT. Subtracts 03CDH (the overlay length) from the high-memory pointer at 4049H, copies the SYS9 code into the newly allocated high-memory block via LDIR from 4E27H, and updates 4310H with the new top address. On return the extended debugger is permanently resident. |
| 4E26H | RST 28H Dispatch Stub The single byte EFH (RST 28H) followed immediately by the command dispatch table. The VTOS overlay dispatcher arrives here with the command character in A and chains through the comparison sequence to reach the correct handler. |
| 4E2DH | O Command - Return to VTOS Calls the hex input routine to consume any trailing input, then jumps to the DOS READY exit at 402DH, returning control from the debugger to the VTOS command prompt. |
| 4E38H | B Command - Block Move Parses up to three hex parameters (source address, destination address, byte count). Loads the three values into HL, DE, and BC respectively and executes LDIR to copy the block. Updates 4060H with the final DE value after the transfer. |
| 4E84H | E Command - Enter Data into Memory Parses a starting address, then enters an interactive loop: displays the current address and its current byte value, accepts a new hex value, stores it, and advances to the next address. Continues until the Carry flag signals end of input. |
| 4EB7H | F Command - Fill Memory Parses start address, end address, and fill byte. Repeatedly stores the fill byte at the current address, increments the address, and loops until the address would exceed the end address (16-bit compare via SBC HL,BC). Returns when the fill range is exhausted. |
| 4ED7H | J Command - Jump Over Next Instruction Byte Loads the saved PC from 407BH, increments it by 1, and stores it back. This advances the resume address by one byte without executing it, skipping a single instruction byte in the suspended program. |
| 4EE3H | L Command - Locate Byte in Memory Searches forward from the current address in 4060H for the next occurrence of a target byte. Optionally accepts a new starting address and target byte from the user. Uses the Z80 CPIR instruction to scan memory. On a match, page-aligns the found address and stores it to 4063H for the display engine. |
| 4F2AH | N Command - Position to Next Relative Block Reads the current address from 4060H, reads the length byte at (address+1), and advances the address pointer past the current load block or relative instruction. Page-aligns the resulting address and updates 4063H. |
| 4F4AH | Q Command - Query/Output Port Parses a port number into C. If only one parameter is given, reads the port via IN A,(C) and displays the result as hex. If a second parameter (data byte) is also given, sends it to the port via OUT (C),L. Covers both the Qii (input) and Qoo,dd (output) forms from the manual. |
| 4F6EH | T Command - Type ASCII Data into Memory Parses an optional starting address, then enters an interactive loop: displays the current byte's ASCII representation, accepts a new character from the keyboard via the hex input engine, stores it, and advances. Skips storage (but still advances) when the input is a space character (20H). Loops until Carry is set by the input routine. |
| 4FADH | V Command - Verify Memory Blocks Parses source address, destination address, and optional byte count. Compares the two blocks byte by byte using a manual loop (LD A,(DE) / CP A,(HL)). On a mismatch, advances both pointers and continues. On full match, returns with both pointers updated to reflect the end of the compared range. |
| 5008H | W Command - Search for Word Address Parses a starting address and a 16-bit word value to find. Searches memory using CPIR for the low byte and then checks the following byte for the high byte match. On a 2-byte match, page-aligns and stores the address to 4063H. |
| 505EH | Disk Transfer Command Handles the extended debugger disk I/O command: d,[c],[s],o,addr,[lth]. Parses the drive number, selects the drive via CALL 478FH, computes the sectors-per-track from the drive parameter block at IY+07H, then parses cylinder, sector, memory address, and length. Dispatches to the appropriate SYS0 read (4777H / 4763H) or write (4768H) routine based on the operation character (R/W/*). |
| 5132H | P Command - Print Memory (Hex and ASCII) Parses start and end addresses. Outputs memory as a formatted hex dump: each line begins with the 4-digit hex address, followed by 16 bytes in hex (grouped in fours with spacing), then a blank column separator, then the same 16 bytes as ASCII characters (non-printable bytes displayed as a period). Continues until the end address is reached or exceeded. |
| 518AH | Hex Input with Separator Detection Shared input engine. Calls the RST 28H dispatcher to read a character, then calls 51A3H to accumulate hex digits into HL. Returns Z set if a separator (comma, space, or CR) was found; Carry set on error or BREAK. Used by nearly every extended command to parse parameters. |
| 51A3H | Hex Nibble Accumulator Reads one byte from (HL), converts the low nibble to ASCII using the ADD 90H / DAA / ADC 40H / DAA technique in reverse (as a decoder), accumulates the result into HL by shifting left 4 and ORing the new nibble, then INCrements HL. Also serves as the shared output-byte-as-hex entry point for the P command's hex dump loop. |
Cross-Reference Notes
SYS9 is called by SYS5/SYS (the resident debugger at 4E00H of its own overlay slot) when the user activates the extended DEBUG facility. SYS9 itself calls the following SYS0 routines: 402DH (DOS READY exit), 478FH (drive select - sets IY to drive parameter block), 4763H (read sector - W operation), 4768H (write sector - * operation), and 4777H (read sector - R operation). SYS9 also calls ROM routines at 0033H (character output) and 003BH (printer/alternate character output, used by the P command's hex dump).
Disassembly:
4E00H - SYS9 Installation: Copy Extended Debugger to High Memory
Entry point called when DEBUG EXT is issued. Allocates space below the high-memory boundary at 4049H, copies the entire SYS9 overlay code into that space via LDIR, and updates the high-memory pointer so VTOS knows the region is in use.
Installation Sequence Begin
The following instructions calculate the destination address for SYS9 in high memory, adjust the high-memory boundary pointer, and copy SYS9's code into place using LDIR.
4E26H - RST 28H Dispatch Stub and Command Dispatch Entry
The single RST 28H byte (EFH) at 4E26H is the SVC call instruction. The VTOS overlay dispatcher calls into SYS9 here. Execution falls through immediately to the dispatch sequence beginning at 4E27H, which compares Register A against each extended command code in turn and branches to the appropriate handler.
4E2DH - O Command: Return to VTOS
Handles the O (capital letter O) extended DEBUG command: "normal return to VTOS". Flushes any pending input from the terminal and then jumps to the VTOS DOS READY exit, cleanly terminating the debug session and returning the user to the VTOS command prompt.
4E38H - B Command: Block Move (Three-Parameter Version)
Handles the B extended DEBUG command: "BLOCK MOVE l bytes of memory from s to d". Parses up to three hex parameters - source address, destination address, and byte count - then performs an LDIR block copy from source to destination for the specified number of bytes.
Parse First Parameter: Source Address
4063H holds the current display address and serves as the first parameter register for the B command. The routine reads any user-typed address into 4063H, falling through to print a comma separator if the address was entered (Z not set from CALL 51A3H).
Parse Second Parameter: Destination Address
4060H holds the destination address for the B command.
Parse Third Parameter: Byte Count
The default byte count is 0100H (256 bytes) if the user provides no third parameter. If Carry is set from the previous accumulator call the third-parameter parse is skipped.
Execute Block Move
HL now holds the byte count. The source address is in 4063H and the destination is in 4060H. These are loaded into the appropriate register pairs for LDIR.
4E84H - E Command: Enter Data into Memory
Handles the E extended DEBUG command: "ENTER data into memory". Accepts a starting address, then enters an interactive loop that displays each memory byte in turn (as its address and current value), waits for a new hex value from the user, stores it, and advances to the next address.
Parse Starting Address
4060H holds the current memory address pointer. If the user types an address it is stored here; otherwise the current value is used as the default.
Loop Start - Enter Data Loop
Each iteration of this loop displays the current address, displays the byte currently at that address as a dash-prefix prompt, waits for the user to type a new hex value, stores it at the current address, and advances HL to the next address. The loop continues until BREAK is pressed.
LOOP BACK to 4E9AH to display the next address, show its current byte, and prompt for another entry. This loop continues byte by byte until BREAK is pressed.
4EB7H - F Command: Fill Memory Range with Byte Value
Handles the F extended DEBUG command: "FILL memory from aaaa to bbbb, inclusively, with the data byte cc". Parses start address, end address, and fill byte, then stores the fill byte at every address from start through end.
Loop Start - Fill Loop
Each iteration stores E at (HL), INCrements HL, and checks whether HL has passed BC (the end address) using a 16-bit subtraction. The loop runs from start through end inclusive.
LOOP BACK to 4ECDH to test the new address against the end and store the next fill byte.
4ED7H - J Command: Jump Over Next Instruction Byte
Handles the J extended DEBUG command: "Jump over the next instruction byte (increment PC by 1)". Simply increments the saved program counter in the SYS5 register save area at 407BH, advancing the resume point past one byte without executing it.
4EE3H - L Command: Locate Next Occurrence of Data Byte
Handles the L extended DEBUG command: "LOCATE the next occurrence of the data byte dd, optionally starting at address aaaa". Searches forward in memory from the current address for the first byte matching the target value, using the Z80 CPIR instruction. On a match, page-aligns the found address and stores it to the display address register 4063H.
Parse Optional Starting Address
4060H holds the current address pointer. If the user provides a new starting address it is stored here; otherwise the search begins from the current 4060H value.
Parse Optional Target Byte
The user may type a new byte value to locate. If no byte is entered, the previous target byte (from 4062H) is used.
Execute Memory Search via CPIR
CPIR searches forward from (HL), comparing each byte to A, decrementing BC after each comparison. With BC=0000H, the instruction searches through the entire 64K address space (wrapping around) until a match is found (Z set) or BC wraps to 0 (NZ).
Match Found - Compute Page-Aligned Display Address
HL points one byte past the match. The address is adjusted back one byte, then its low byte is page-aligned (low 6 bits cleared) to produce a 64-byte-aligned display address for the memory dump window.
4F2AH - N Command: Position to Next Relative Block or Instruction
Handles the N extended DEBUG command: "position to the NEXT relative block/instruction (such as JR X or load block)". Reads the current address from 4060H, fetches the length byte at (address+1), and advances the address pointer past the current record. The result is page-aligned and stored to 4063H for display.
4F4AH - Q Command: Query Input Port / Output to Port
Handles the Q extended DEBUG command in both its forms: "Qii" (query input port ii) and "Qoo,dd" (send data byte dd to output port oo). Parses the port number into C, then either reads the port via IN A,(C) and displays the result, or writes the second parameter to the port via OUT (C),L.
4F6EH - T Command: Type ASCII Data into Memory
Handles the T extended DEBUG command: "TYPE ASCII data into memory, optionally starting at address aaaa". Accepts an optional starting address, then enters an interactive loop that displays each memory byte's ASCII value with a dash prompt and stores keyboard input characters directly into memory. A space input skips (does not overwrite) the current byte but advances the pointer.
Loop Start - Type Data Loop
Each iteration outputs a 1EH cursor control, displays the current byte as an ASCII character (or period for non-printable), outputs a dash prompt, reads an input character, stores it (unless it is a space), advances the pointer, and loops.
4FADH - V Command: Verify Memory Blocks
Handles the V extended DEBUG command: "VERIFY the memory block of lth bytes at aaaa to the memory block at bbbb". Parses two address parameters and an optional byte count, then compares the two memory blocks byte by byte. Both address pointers advance through their respective ranges, updated in 4063H and 4060H as the comparison proceeds.
Parse First Parameter: Source Address
Parse Second Parameter: Destination Address
Parse Optional Third Parameter: Byte Count
The default count is 0000H. With BC=0000H the comparison loop will run for 65536 iterations (the full address space) if no mismatch is found.
Execute Verify Loop
HL = byte count (BC after the next two instructions). Source at 4063H (in HL after load), destination at 4060H (in DE). Each byte pair is compared; the loop runs until BC=0 or a mismatch is found.
Loop Start - Verify Loop
Each iteration reads one byte from each block and compares them. On a mismatch the loop continues (both pointers advance). On a full match (BC exhausted), both updated pointers are stored back.
If the NZ flag is set (BC is not yet zero - more bytes to compare), LOOP BACK to 4FF5H to compare the next pair.
5008H - W Command: Search Memory for Word Address
Handles the W extended DEBUG command: "search memory for the WORD ADDRESS dddd, optionally starting at address aaaa". Parses a starting address and a 16-bit word value, then searches memory for a two-byte sequence matching the low byte followed by the high byte (little-endian word match) using CPIR. On a match, page-aligns the address and stores it to 4063H.
Parse Starting Address and Target Word
The W command needs a starting address and the 16-bit word to find. The starting address is read from 4060H (with INC HL to start searching at the next address). The word to find is stored split across 4062H (low byte) and 405FH (high byte).
Parse Optional Target Word
If a new target word is entered it overwrites the defaults in HL. The parsed word is then split back into 4062H (low byte) and 405FH (high byte) for future W commands.
Execute Word Search via CPIR (Two-Phase)
The search uses CPIR to find the low byte, then manually checks that the following byte matches the high byte. This implements a little-endian 16-bit word search: low byte at address aaaa, high byte at aaaa+1.
Word Match Found - Compute Address and Page-Align
HL points one past the high byte (i.e., two bytes past the start of the found word). The routine steps back two bytes to reach the word start, stores it, then page-aligns for display.
505EH - Disk Transfer Command: d,[c],[s],o,addr,[lth]
Handles the unnamed extended DEBUG disk transfer command: "transfer a cylinder or sector to or from a disk unit". The command syntax is d,[c],[s],o,addr,[lth] where d is the drive number (0-7), c is the optional cylinder, s is the optional sector, o is the operation (R=read, W=write, *=directory write), addr is the memory address, and lth is the optional length in sectors. Dispatches to the appropriate SYS0 I/O routine based on the operation character.
Drive Number Dispatch Check
The disk command is triggered not by a letter but by a decimal digit (0-7) as the command character. The check tests that A is in the range 30H-37H (ASCII digits '0' to '7').
Select Drive and Compute Sectors-Per-Track
The drive number is converted from ASCII to binary, the drive parameter block is selected via CALL 478FH, and the sectors-per-track value is derived from IY+07H (the sectors/track and interleave byte in the drive parameter block).
Loop Start - Multiply Loop (Sectors-per-Track Calculation)
This loop computes A = H * B by repeated addition. B is the sectors-per-track count; H is the per-sector value. The result in A is the total sectors per cylinder.
DECrement B and LOOP BACK to 507EH if B is not zero. When B reaches zero, A holds the total sectors-per-cylinder (sectors-per-track * per-sector-value).
Parse Command Parameters: Cylinder, Sector, Operation, Address, Length
The disk command now parses the remaining parameters. 518AH reads the full input line; 51A3H accumulates hex digits. The operation character (R/W/*) is stored in B. The parsed track, sector, and length are used to drive the SYS0 I/O routines.
Parse Optional Length Parameter
If the user specifies a length (number of sectors) it overrides the default count.
Dispatch to I/O Routine Based on Operation Character
The operation character in B is checked against R (52H), W (57H), and * (2AH). Each dispatches to the appropriate SYS0 read or write routine. Unknown operation characters fall through to the sector-step loop which processes the transfer iteratively.
Loop Start - Multi-Sector Transfer Loop
For operations that do not immediately dispatch to a single-call I/O routine, the following loop advances through DE (track and sector), decrements the sector count, and repeats. This handles multi-sector transfers by iterating one sector at a time.
If the NZ flag is set (remaining sector count is not yet zero), LOOP BACK to 50CDH to dispatch the next sector transfer. When the count reaches zero, fall through.
Post-Read: Update Display Addresses
For a read operation, after all sectors have been loaded into memory, the display address registers are updated to point at the start of the loaded data so the user can immediately examine the result.
Single I/O Dispatch Routines
The following three short sections handle direct single-call dispatches to the three SYS0 I/O routines for the R, W, and * operations respectively. Each calls the SYS0 routine and then either loops back for another sector or exits.
I/O Error Display and Acknowledge
On an I/O error, the routine displays a space, the error code as two hex digits, an asterisk pair flanking the current operation character, and then waits for the user to press any key before returning to the multi-sector loop or exiting.
5132H - P Command: Print Memory in Hex and ASCII
Handles the P extended DEBUG command: "PRINT memory from aaaa through bbbb, inclusively, in both hex and ASCII". Parses two address parameters, then outputs a formatted hex dump: each line shows a 4-digit hex address followed by 16 bytes in hex (with grouping spaces every 4 bytes), then a separator, then the same 16 bytes as ASCII characters (non-printable bytes shown as a period). The dump continues line by line until the end address is reached or exceeded. This section also contains the shared hex output subroutines used throughout SYS9.
Parse Start and End Addresses
The P command requires exactly two parameters: start address and end address. If either is zero (Z set from 51A3H) the command returns immediately.
Align Start Address to 16-Byte Boundary
The P command's hex dump lines are 16 bytes wide. The start address is rounded down to the nearest 16-byte boundary (clearing the low 4 bits) so the first displayed line begins at a clean hex address ending in 0.
Loop Start - Outer Dump Loop (One Line = 16 Bytes per Iteration)
Each outer iteration saves HL (the current 16-byte line start address), outputs the 4-digit hex address, then outputs 16 bytes in hex, then outputs the 16 bytes as ASCII, then checks whether the end address BC has been reached.
Inner Hex Byte Output Loop Entry (from Line-Wrap)
5191H is the re-entry point for subsequent lines of the dump (reached via the JR C,5191H at 51E7H). It re-enters the outer loop at 514EH to begin a new line.
Loop Start - Inner Hex Byte Output Loop (16 bytes per line)
Each iteration of this inner loop outputs one byte as two hex digits, followed by a space, then checks for group spacing (extra space every 4 bytes) and line-end (after 16 bytes). HL points to the current byte; INC HL advances after each byte.
Shared Entry Point: 51A3H - Low Nibble Output
51A3H is both the second half of the per-byte hex output (outputting the low nibble after the high nibble has been sent) and a shared entry point used by the extended command parameter parsers throughout SYS9 to accumulate a hex digit from input into HL. When called from 51A0H it outputs the low nibble of (HL); when called directly by parameter parsers it reads one more nibble from the keyboard and accumulates it.
LOOP BACK to 5193H to output the next byte in hex. This inner loop continues until the address reaches a 16-byte boundary, producing the full hex portion of the current line.
ASCII Portion of Dump Line
After 16 hex bytes have been output, a separator space is added and then the same 16 bytes are displayed again as ASCII characters. The saved HL from the PUSH at 514EH is popped to restore the line-start address.
Loop Start - ASCII Byte Output Loop (16 bytes per line)
Each iteration reads one byte, determines whether it is printable ASCII (20H-7FH), outputs the character or a period substitute, and advances HL. The loop ends when the address reaches the same 16-byte boundary it stopped at during the hex output.
If the NZ flag is set (lower 4 bits are not zero - fewer than 16 bytes output on this line), LOOP BACK to 51C9H to output the next ASCII character.
End of Line - Carriage Return and End-of-Dump Test
After the 16th ASCII character has been output, a carriage return ends the line. The current address HL is then compared against the end address BC; if HL >= BC the dump is complete.
If the Carry flag is set (current address is still before the end address), LOOP BACK to 5191H which jumps to 514EH to begin the next dump line. When Carry is clear (current address has reached or passed the end address), fall through to output the trailing carriage returns and return.
End of Dump - Output Trailing Carriage Returns
Three carriage returns are output via a fall-through and a final JP 003BH to terminate the dump display cleanly.