TRS-80 DOS - LDOS 5.3.1 for the Model I - SYS9/SYS Disassembled
Page Customization
Page Index
SYS9/SYS
Other Navigation
Summary:
LDOS 5.3.1 SYS9/SYS Disassembly - Extended DEBUG Commands (Model I)
SYS9/SYS is what the DEBUG (EXT) option installs. On its own the DEBUG monitor in SYS5/SYS understands a small set of single-key commands; anything else it does not recognise is passed to a resident vector at 4310H, which by default holds 4876H, an OR A followed by a RET that does nothing at all. SYS9/SYS captures that vector and hangs thirteen further commands off it.
The overlay is unusual in that almost none of it runs where it is loaded. The first thirty-eight bytes are an installer: they check the vector still holds its default, so that a second DEBUG (EXT) cannot install the extension twice, lower HIGH$ at 4049H by 03D8H bytes to reserve space at the top of memory, copy the remaining 03D8H bytes of the overlay up into it, and point 4310H at the copy. The overlay then returns and is never needed again; the extension it left behind lives in high memory for as long as the machine is up.
What gets copied is a properly formed resident module. Its first byte is 18H, the relative jump that every LDOS module header begins with, hopping over the header to the code; the word at offset 2 is the link to the previous module, which the installer fills in with the value HIGH$ had before it was lowered; the byte at offset 4 holds the name length in its low nibble; and the six characters that follow spell EXTDBG. That is exactly the layout the module lookup in SYS10/SYS walks when a program asks whether a named module is resident, so the extension can be found by name like any driver or filter.
Copying code to an address decided at run time only works if the code does not care where it is, and this module is written so that it does not. Every transfer inside it is a relative jump, of which there are sixty, and every absolute address it names belongs to something that does not move: the ROM at 0033H and 003BH, the resident core at 402DH, 4763H, 4768H, 4777H and 478FH, and six routines in the SYS5/SYS DEBUG overlay. It never refers to itself by an absolute address at all.
Those six SYS5/SYS routines are worth spelling out, because at first sight they look like references into the module itself. The addresses 5189H, 51A4H, 51D1H, 51D5H, 51DAH and 51F1H fall inside the range the overlay occupies when it is loaded, but the module never executes at those addresses once it has been relocated. They are the DEBUG monitor's own single-key command reader, hexadecimal parameter parser and hexadecimal display routines, which sit at fixed addresses in the transient overlay region whenever DEBUG is resident. The extension borrows all of its parsing and display from them, which is why it fits into 03D8H bytes while offering as much as it does.
It borrows DEBUG's variables too. The memory-modify pointer at 4060H and the memory-display window at 4063H in the save area DBGSV$ are read as the default operands of most commands and written back afterwards, so that the extended commands and DEBUG's own display stay in step; 4062H and 405FH are used as scratch to remember the last value searched for; 405EH is the screen-layout flag, which the disk read command sets to 73H so that DEBUG comes back showing the sector that was just read; and 407BH is the saved program counter of the program under test, which the j command increments.
The commands themselves fall into three groups. Seven work on memory: b moves a block, f fills a range, e and t examine and alter it as hexadecimal and as text, l and w search for a byte and for a two-byte value, and v compares two blocks and stops at the first difference. Three are about control and hardware: j steps the saved program counter over a byte, q reads and writes Z80 ports, and o leaves DEBUG through the resident @EXIT vector. One, n, walks from one record of an LDOS load module to the next by decoding its length byte. The digits 0 to 7 open raw disk sector access on that drive, and @ prints a range of memory to the printer.
The overlay spans 4E00H through 51FDH plus the usual two-byte load block at 4BC9H, and its transfer address is 4E00H. It may be purged if the extended commands are not wanted, and keeping it serves no purpose if SYS5/SYS has itself been purged.
Extended Commands
| Key | Entry | Action |
|---|---|---|
| b | 4E3CH | Block move. Source, destination and length, any of which may default to where DEBUG is pointing; 256 bytes if no length is given. |
| e | 4E88H | Examine and enter bytes in hexadecimal, one at a time, with a hyphen prompt. Entering nothing steps on. |
| f | 4EBBH | Fill a range of memory with one byte value. |
| j | 4EDBH | Add one to the saved program counter at 407BH, skipping a byte of the program under test. |
| l | 4EE7H | Locate a byte value, remembering it at 4062H so a repeat searches for the same one. |
| n | 4F2EH | Step to the next record of an LDOS load module, decoding its length byte. |
| o | 4E31H | Leave DEBUG through the resident @EXIT vector at 402DH, after a confirming keystroke. |
| q | 4F4FH | Read a Z80 port, or write a value to it if one is given. |
| t | 4F73H | Examine and enter memory as text. Patches the DEBUG key reader so ordinary characters pass through; a space leaves a byte alone. |
| v | 4FBBH | Compare two blocks and stop at the first difference, leaving a pointer on each. |
| w | 5016H | Locate a two-byte value, remembering it at 4062H and 405FH. |
| 0-7 | 506CH | Raw disk sector access on that drive: r read, w write, * write with a deleted-data address mark. Defaults to the directory cylinder and to a whole cylinder. |
| @ | 513EH | Print a range of memory to the printer, sixteen bytes a line, in hexadecimal and as characters. |
Memory Map
| Address Range | Purpose |
|---|---|
| 4BC9H-4BCAH 2 bytes | Load block deposited into the resident padding area below the RST 28H dispatcher. SYS9/SYS writes FEH and 03H there. |
| 4E00H-4E25H 38 bytes | The installer. Runs once in the transient overlay region and is then discarded. |
| 4E26H-4E30H 11 bytes | Resident module header: 18H signature, link word, name length and the name EXTDBG. |
| 4E31H-513DH 781 bytes | Command dispatch and the memory, control, load-module and disk commands. |
| 513EH-51FDH 192 bytes | The printer memory dump. |
| 4E26H-51FDH 984 bytes | The whole relocatable module, which the installer copies to HIGH$ less 03D8H plus one. |
Borrowed SYS5/SYS Routines and DEBUG Variables
| Address | Use |
|---|---|
| 5189H | SYS5/SYS single-key command reader. Returns the key in Register A, CARRY set if the operator broke out. |
| 51A4H | SYS5/SYS hexadecimal parameter parser. Returns the value in Register Pair HL, Z set when nothing was typed so a default can be used, CARRY set when the command was ended. |
| 51D1H | SYS5/SYS byte display, which also advances Register Pair HL. |
| 51D5H | SYS5/SYS four-digit display of Register Pair HL. |
| 51DAH | SYS5/SYS two-digit display of Register A. |
| 51F1H | SYS5/SYS display helper that prepares the screen line. |
| 5196H | A byte inside the SYS5/SYS key reader that the t command patches to 6FH and back to 0EFH so that ordinary text is not treated as commands. |
| 405EH | DBGSV$ screen-layout flag. Set to 73H after a disk read so DEBUG returns showing the sector. |
| 405FH | DBGSV$ scratch. Sectors per cylinder during a disk command; the high byte of the last word searched for. |
| 4060H-4061H | DBGSV$ memory-modify pointer. The default operand of most commands and where each leaves its result. |
| 4062H | DBGSV$ scratch. The last byte searched for, and the count of sectors during a disk command. |
| 4063H-4064H | DBGSV$ memory-display window address, moved so the screen follows what a command found. |
| 407BH-407CH | DBGSV$ saved program counter of the program under test, incremented by the j command. |
| 4310H-4311H | Resident vector for an unrecognised DEBUG command. Default 4876H; this overlay points it at the installed module. |
| 4049H-404AH | Resident HIGH$, lowered by 03D8H to reserve the module's space. |
Cross-References
SYS9/SYS is loaded by RST 28H request code 8BH, issued by the DEBUG command in SYS7/SYS when its EXT option is given and by SYS5/SYS itself. Unlike the other overlays it does not mask the request code at all, since it has only one thing to do. It is undone by the DEBUG OFF and RESET paths in SYS7/SYS, which put 4876H back into the vector at 4310H.
Its whole working life is spent inside SYS5/SYS, whose command loop calls it, whose parser and display routines it borrows, and whose save area DBGSV$ it shares. For raw disk access it calls the resident primitives in SYS0/SYS: GETDCT at 478FH for the drive geometry, and RDSECT at 4777H, WRSECT at 4763H and WRPROT at 4768H for the transfers, the last of these being the same deleted-data write that SYS8/SYS and SYS10/SYS use for the Granule Allocation Table and Hash Index Table. The module header it builds is read by the resident module lookup in SYS10/SYS, request code 0BCH.
Disassembly:
4BC9H - Overlay Load Block
The two bytes every SYS overlay deposits into the padding area of the resident core immediately below the RST 28H dispatcher. They are data, not instructions, and are never executed.
4E00H - Install the Extended Command Module
The overlay itself does almost nothing: it checks that no extension is already installed, reserves room at the top of memory, copies its own body up there as a resident module named EXTDBG, links it into the chain of resident modules and points the resident vector at 4310H at it. Once that is done the overlay is finished with and the extension lives on in high memory for as long as the machine is up.
Store the present value of HIGH$ into 4E28H, which is the link word of the module header built below. Chaining the new module to the previous top of memory is what lets the module search in SYS10/SYS walk from one resident module to the next.
4E26H - Resident Module Header
The first eleven bytes of the relocatable module, in the standard LDOS resident module format. The installer above copies this header and everything after it into high memory, and the module lookup in SYS10/SYS walks exactly these fields when a program asks whether a named module is present.
The link to the previous module in the chain. The installer writes the value HIGH$ held before it was lowered into these two bytes at 4E0DH. The lookup in SYS10/SYS follows this word, incrementing it to reach the next header, and treats FFFFH as the end of the chain.
4E31H - Command Dispatch and the o Command
This is the first byte the resident vector reaches, and it is where SYS5/SYS DEBUG sends every command character it does not recognise, with that character in Register A. The tests run one after another down the whole of the module. The first of them, o, asks for confirmation and then leaves DEBUG altogether through the resident @EXIT vector.
Position-Independent Code
Everything from here to the end of the module runs at an address decided when the extension is installed, not at the address shown in this listing. That is possible because every transfer inside the module is a relative jump, of which there are sixty, and every absolute address it names belongs to something that does not move. In particular the addresses 5189H, 51A4H, 51D1H, 51D5H, 51DAH and 51F1H, which appear throughout the code below, are not references to this module. They are routines in the SYS5/SYS DEBUG overlay, which occupies the transient region whenever DEBUG is resident, and they provide all of this extension's keyboard parsing and hexadecimal display.
4E3CH - The b Command, Block Move
Moves a block of memory. The source, destination and length are each collected in turn, any of them defaulting to what DEBUG is already pointing at, and anything defaulted is echoed so the operator can see what was assumed. The destination pointer is left advanced so that repeating the command carries straight on.
4E88H - The e Command, Examine and Enter
Walks through memory a byte at a time, showing each one and offering a hyphen prompt at which a replacement may be typed. Typing nothing leaves the byte as it stands and moves on, so the command doubles as a slow scroll through memory.
GOSUB to the display routine at 51D1H inside the SYS5/SYS DEBUG overlay, which shows the byte at the address in Register Pair HL and leaves Register Pair HL advanced past it.
4EBBH - The f Command, Fill Memory
Fills a range of memory with a single byte value. The start, end and fill byte are collected in turn, and the loop stops as soon as the current address reaches the end of the range.
Save the current address while it is compared against the end of the range.
4EDBH - The j Command, Skip a Byte
Adds one to the program counter that DEBUG saved when it was entered, so that the program under test restarts one byte further on. It is the way past a breakpoint opcode or an instruction that is not to be executed.
4EE7H - The l Command, Locate a Byte
Searches forward through memory for a byte value, using the Z80 block-compare instruction with a count of zero so that the whole of memory is covered. The value is remembered so that repeating the command searches for the same one, and a match leaves both the editing pointer and the display window sitting on it.
4F2EH - The n Command, Next Load Record
Steps to the next record of an LDOS load module. The byte after the record type is its length, and a length below three means the count has wrapped and 256 must be added back, which is the same convention the system loader itself follows. The command is what makes it practical to walk through a program file in memory.
4F4FH - The q Command, Port Input and Output
Reads or writes a Z80 port. Giving only a port number reads it and shows the value; giving a port number and a value writes it. Because the port number is taken into Register C the full sixteen-bit port addressing of the Z80 is available.
4F73H - The t Command, Text Entry
The character-oriented counterpart of the e command, for patching text rather than opcodes. Each byte is shown as a character, a hyphen prompt invites a replacement, and typing a space leaves the byte alone. To let ordinary text through, the routine patches one byte inside the DEBUG keyboard reader before each keystroke and puts it back afterwards.
GOSUB to the display helper at 51F1H inside the SYS5/SYS DEBUG overlay, which prepares the screen line for the value about to be shown.
Store 6FH over the byte at 5196H inside the SYS5/SYS DEBUG overlay. That turns the reader's own filtering instruction into one which lets every character through, so that the text being entered is taken literally instead of being treated as DEBUG commands.
Store 0EFH back over the byte at 5196H, restoring the reader in the SYS5/SYS DEBUG overlay to its normal filtering behaviour now that the character has been taken.
4FBBH - The v Command, Verify Two Blocks
Compares two blocks of memory and stops at the first difference, leaving one pointer on each block so that the mismatch can be examined at once. Running to the end without a difference leaves both pointers past the blocks.
Fetch the next byte of the second block, addressed by Register Pair DE, into Register A.
Loop End
5016H - The w Command, Locate a Word
The two-byte form of the l command. The block compare finds a byte matching the low half and the byte after it is then tested against the high half, the search resuming from the same place when only the low half matched.
Fetch the starting address from 4060H, the memory-modify pointer in DBGSV$, into Register Pair HL.
Loop End
506CH - The Disk Sector Commands
A digit key names a drive and opens the disk commands, which read and write raw sectors. The drive geometry is read from the Drive Code Table to work out how many sectors a cylinder holds, so that a transfer can roll from one cylinder to the next and so that naming no sector transfers a whole cylinder. Three operations are offered: r reads, w writes, and * writes with a deleted-data address mark as the directory cylinder is recorded. An error is shown bracketed by asterisks and the operator decides whether to carry on, and after a read DEBUG is switched to its full-memory screen showing the data.
Add the sectors-per-track figure in Register H to the running total in Register A.
Loop End
Copy the operation letter from Register B into Register A.
Loop End
513EH - The @ Command, Print a Memory Range
Prints a range of memory to the printer, sixteen bytes to a line, as a four-digit address followed by the bytes in hexadecimal grouped in fours and then the same bytes as characters, with a full stop standing in for anything that cannot be printed. The hexadecimal digits are produced without a lookup table by the classic add-90H, decimal-adjust, add-40H-with-carry, decimal-adjust sequence.
Save the address of the line about to be printed, which is needed again at 51D2H when the same sixteen bytes are shown as characters.
Fetch the next byte of the dump, addressed by Register Pair HL, into Register A.
Fetch the next byte of the line, addressed by Register Pair HL, into Register A.
Loop End