TRS-80 DOS - LDOS 5.3.1 for the Model I - SYS9/SYS Disassembled

Page Customization

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

KeyEntryAction
b4E3CHBlock move. Source, destination and length, any of which may default to where DEBUG is pointing; 256 bytes if no length is given.
e4E88HExamine and enter bytes in hexadecimal, one at a time, with a hyphen prompt. Entering nothing steps on.
f4EBBHFill a range of memory with one byte value.
j4EDBHAdd one to the saved program counter at 407BH, skipping a byte of the program under test.
l4EE7HLocate a byte value, remembering it at 4062H so a repeat searches for the same one.
n4F2EHStep to the next record of an LDOS load module, decoding its length byte.
o4E31HLeave DEBUG through the resident @EXIT vector at 402DH, after a confirming keystroke.
q4F4FHRead a Z80 port, or write a value to it if one is given.
t4F73HExamine and enter memory as text. Patches the DEBUG key reader so ordinary characters pass through; a space leaves a byte alone.
v4FBBHCompare two blocks and stop at the first difference, leaving a pointer on each.
w5016HLocate a two-byte value, remembering it at 4062H and 405FH.
0-7506CHRaw 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.
@513EHPrint a range of memory to the printer, sixteen bytes a line, in hexadecimal and as characters.

Memory Map

Address RangePurpose
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

AddressUse
5189HSYS5/SYS single-key command reader. Returns the key in Register A, CARRY set if the operator broke out.
51A4HSYS5/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.
51D1HSYS5/SYS byte display, which also advances Register Pair HL.
51D5HSYS5/SYS four-digit display of Register Pair HL.
51DAHSYS5/SYS two-digit display of Register A.
51F1HSYS5/SYS display helper that prepares the screen line.
5196HA 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.
405EHDBGSV$ screen-layout flag. Set to 73H after a disk read so DEBUG returns showing the sector.
405FHDBGSV$ scratch. Sectors per cylinder during a disk command; the high byte of the last word searched for.
4060H-4061HDBGSV$ memory-modify pointer. The default operand of most commands and where each leaves its result.
4062HDBGSV$ scratch. The last byte searched for, and the count of sectors during a disk command.
4063H-4064HDBGSV$ memory-display window address, moved so the screen follows what a command found.
407BH-407CHDBGSV$ saved program counter of the program under test, incremented by the j command.
4310H-4311HResident vector for an unrecognised DEBUG command. Default 4876H; this overlay points it at the installed module.
4049H-404AHResident 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.

4BC9
DEFB FEH, 03H FE 03
Two-byte load block written into the resident padding area at 4BC9H through 4BCAH by the overlay loader as SYS9/SYS is brought into memory. The bytes are data and are not part of the executable image, which begins at 4E00H.

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.

4E00
LD HL,(4310H) 2A 10 43
Fetch the resident vector word at 4310H into Register Pair HL. SYS5/SYS DEBUG jumps through that word whenever the operator types a command it does not recognise, so it is the hook this overlay has to capture.
4E03
XOR A AF
Set Register A to ZERO and clear all flags, in particular the CARRY flag, so the addition below is exact.
4E04
LD DE,0B78AH 11 8A B7
Load Register Pair DE with 0B78AH, which is the two-complement negative of 4876H, the address the resident vector holds while no extension is installed.
4E07
ADC HL,DE ED 5A
Add Register Pair DE to Register Pair HL with carry. The result is zero only when the vector still holds its default 4876H, that is only when no extension is installed yet.
4E09
RET NZ C0
If the NZ FLAG has been set the vector already points at something else, so an extension is installed and there is nothing to do; return to the RST 28H dispatcher.
4E0A
LD HL,(4049H) 2A 49 40
Fetch HIGH$ from 4049H into Register Pair HL. That resident word is the current top of user memory, and everything installed in high memory is fitted in below it.
4E0D
LD (4E28H),HL 22 28 4E
Self-Modifying Code
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.
4E10
LD BC,03D8H 01 D8 03
Load Register Pair BC with 03D8H, the length of the module that is about to be copied into high memory: the header at 4E26H together with all the code that follows it, up to and including 51FDH.
4E13
XOR A AF
Set Register A to ZERO and clear all flags, in particular the CARRY flag, so the subtraction below is exact.
4E14
SBC HL,BC ED 42
Subtract the module length from HIGH$ in Register Pair HL, leaving the address of the last byte still available to the user once the module has been fitted in above it.
4E16
LD (4049H),HL 22 49 40
Store the reduced value back into HIGH$ at 4049H, reserving the space so that nothing loaded later can overwrite the module.
4E19
INC HL 23
Increment Register Pair HL so it addresses the first byte of the reserved area, which is where the module is to be placed.
4E1A
PUSH HL E5
Save that destination address; it is needed again at 4E21H to point the resident vector at the installed module.
4E1B
EX DE,HL EB
Exchange Register Pair DE with Register Pair HL, putting the destination address into Register Pair DE ready for the block move.
4E1C
LD HL,4E26H 21 26 4E
Load Register Pair HL with 4E26H, the first byte of the module image inside this overlay, which is the source of the block move.
4E1F
LDIR ED B0
Block move with increment: copy the 03D8H bytes of the module from the overlay into the space reserved at the top of memory. The module is written so that this move is safe, every transfer inside it being relative and every absolute address it names belonging to the resident core, the ROM or the SYS5/SYS DEBUG overlay.
4E21
POP HL E1
Recover the address at which the module was installed.
4E22
LD (4310H),HL 22 10 43
Store it into the resident vector at 4310H. From now on every DEBUG command that SYS5/SYS does not recognise is passed to this module, which is what the DEBUG (EXT) option means.
4E25
RET C9
Return to the RST 28H dispatcher. The overlay itself is finished with; the extension it installed lives on in high memory.

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.

4E26
DEFB 18H, 09H 18 09
The module signature. 18H is the opcode of a relative jump and 09H its displacement, so the first instruction of every resident module hops over its own header, here landing on the command dispatch at 4E31H. The module lookup in SYS10/SYS tests this byte for 18H to satisfy itself that it is looking at a header at all.
4E28
DEFW 0000H 00 00
Self-Modifying Code
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.
4E2A
DEFB 06H 06
The module name length. The lookup in SYS10/SYS takes the low nibble of this byte, here six, and compares it against the length of the name being searched for before troubling to compare the characters.
4E2B
DEFM "EXTDBG" 45 58 54 44 42 47
The module name. A program can ask the resident module lookup, request code 0BCH, whether EXTDBG is installed, and the address returned is that of the byte after this name, which for this module is the command dispatch itself.

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.

4E31
CP 6FH FE 6F
Register A holds the command character that SYS5/SYS DEBUG could not recognise. Compare it against 6FH, the ASCII code for o. If Register A equals 6FH, the Z FLAG is set.
4E33
If the NZ FLAG has been set the command is not o, so JUMP to 4E3CH to try the next one.
4E35
GOSUB to the single-key command reader at 5189H inside the SYS5/SYS DEBUG overlay, which waits for one keystroke and returns it in Register A with the CARRY flag set if the operator broke out. The command asks for confirmation before leaving DEBUG.
4E38
RET NC D0
If the NO CARRY FLAG has been set the operator did not confirm, so return to DEBUG and carry on as before.
4E39
JUMP to the resident @EXIT vector at 402DH, leaving DEBUG and the program under test behind and returning to the command interpreter.

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.

4E3C
CP 62H FE 62
Compare the command character in Register A against 62H, the ASCII code for b. If Register A equals 62H, the Z FLAG is set.
4E3E
If the NZ FLAG has been set the command is not b, so JUMP to 4E88H to try the next one.
4E40
LD HL,(4063H) 2A 63 40
Fetch the memory-display window address that SYS5/SYS DEBUG keeps at 4063H in its save area DBGSV$, which fixes what the screen is showing into Register Pair HL, to serve as the default source address of the move.
4E43
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the source address.
4E46
RET C D8
If the CARRY FLAG has been set the operator ended the command, so return to DEBUG without moving anything.
4E47
LD (4063H),HL 22 63 40
Store the source address back into 4063H, the memory-display window address in DBGSV$, so the display follows the operation.
4E4A
If the NZ FLAG has been set the operator typed the address, so it is already on the screen; JUMP to 4E54H and skip echoing it.
4E4C
GOSUB to the display routine at 51D5H inside the SYS5/SYS DEBUG overlay, which shows Register Pair HL on the screen as four hexadecimal digits. No address was typed, so the default taken from the display window is echoed for the operator to see.
4E4F
LD A,2CH 3E 2C
Load Register A with 2CH, the ASCII code for a comma.
4E51
GOSUB to the ROM character-output routine at 0033H to display the comma, separating this value from the next on the command line.
4E54
LD HL,(4060H) 2A 60 40
Fetch the memory-modify pointer that SYS5/SYS DEBUG keeps at 4060H in its save area DBGSV$, which is where its own m command and the memory display are currently positioned into Register Pair HL, to serve as the default destination address of the move.
4E57
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the destination address.
4E5A
LD (4060H),HL 22 60 40
Store the destination address into 4060H, the memory-modify pointer in DBGSV$.
4E5D
If the NZ FLAG has been set the operator typed the address, so JUMP to 4E69H and skip echoing it.
4E5F
PUSH AF F5
Save the flags from the parse, which the echo below would otherwise destroy.
4E60
GOSUB to the display routine at 51D5H inside the SYS5/SYS DEBUG overlay, which shows Register Pair HL on the screen as four hexadecimal digits. The defaulted destination address is echoed.
4E63
LD A,2CH 3E 2C
Load Register A with 2CH, the ASCII code for a comma.
4E65
GOSUB to the ROM character-output routine at 0033H to display the comma, separating this value from the next on the command line.
4E68
POP AF F1
Restore the flags from the parse.
4E69
LD HL,0100H 21 00 01
Load Register Pair HL with 0100H, that is 256 bytes, the length the move uses when the operator gives none.
4E6C
If the CARRY FLAG has been set the operator ended the command after the destination, so JUMP to 4E73H to use that default length.
4E6E
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the number of bytes to move.
4E71
If the NZ FLAG has been set a length was typed and is already on the screen, so JUMP to 4E78H to perform the move.
4E73
PUSH HL E5
Save the length while it is echoed.
4E74
GOSUB to the display routine at 51D5H inside the SYS5/SYS DEBUG overlay, which shows Register Pair HL on the screen as four hexadecimal digits. The defaulted length is echoed.
4E77
POP HL E1
Recover the length.
4E78
LD B,H 44
Copy the high byte of the length from Register H into Register B.
4E79
LD C,L 4D
Copy the low byte from Register L into Register C, so Register Pair BC holds the count the block move needs.
4E7A
LD HL,(4063H) 2A 63 40
Fetch the source address from 4063H, the memory-display window address in DBGSV$, into Register Pair HL.
4E7D
LD DE,(4060H) ED 5B 60 40
Fetch the destination address from 4060H, the memory-modify pointer in DBGSV$, into Register Pair DE.
4E81
LDIR ED B0
Block move with increment: copy Register Pair BC bytes from the source addressed by Register Pair HL to the destination addressed by Register Pair DE. Both pointers are left one past the end of their blocks.
4E83
LD (4060H),DE ED 53 60 40
Store the advanced destination pointer back into 4060H, so a repeat of the command carries on where this one finished.
4E87
RET C9
Return to DEBUG with the block moved.

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.

4E88
CP 65H FE 65
Compare the command character in Register A against 65H, the ASCII code for e. If Register A equals 65H, the Z FLAG is set.
4E8A
If the NZ FLAG has been set the command is not e, so JUMP to 4EBBH to try the next one.
4E8C
LD HL,(4060H) 2A 60 40
Fetch the memory-modify pointer that SYS5/SYS DEBUG keeps at 4060H in its save area DBGSV$, which is where its own m command and the memory display are currently positioned into Register Pair HL, to serve as the default address at which entry begins.
4E8F
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the address at which to start entering bytes.
4E92
LD (4060H),HL 22 60 40
Store that address into 4060H, the memory-modify pointer in DBGSV$.
4E95
RET C D8
If the CARRY FLAG has been set the operator ended the command, so return to DEBUG.
4E96
If the NZ FLAG has been set the operator typed the address, so JUMP to 4E9EH and skip echoing it.
4E98
GOSUB to the display routine at 51D5H inside the SYS5/SYS DEBUG overlay, which shows Register Pair HL on the screen as four hexadecimal digits. The defaulted address is echoed.
4E9B
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.
4E9E
LD A,1EH 3E 1E
Load Register A with 1EH, the video control code that erases from the cursor to the end of the line, clearing away the rest of the command.
4EA0
GOSUB to the ROM character-output routine at 0033H to send that control code to the video device.
4EA3
Loop Start
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.
4EA6
DEC HL 2B
Step Register Pair HL back to the byte just shown, which is the one the operator is being invited to replace.
4EA7
LD A,2DH 3E 2D
Load Register A with 2DH, the ASCII code for a hyphen, the prompt that separates the old value from the new.
4EA9
GOSUB to the ROM character-output routine at 0033H to display that prompt.
4EAC
EX DE,HL EB
Exchange Register Pair DE with Register Pair HL, parking the memory pointer in Register Pair DE while the parser uses Register Pair HL.
4EAD
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the replacement byte.
4EB0
EX DE,HL EB
Exchange Register Pair DE with Register Pair HL again, bringing the memory pointer back into Register Pair HL and the typed value into Register Pair DE.
4EB1
If the Z FLAG has been set nothing was typed, so the byte is to be left as it stands; JUMP to 4EB4H.
4EB3
LD (HL),E 73
Store the low byte of the typed value from Register E into memory at the address in Register Pair HL, replacing the byte that was there.
4EB4
RET C D8
If the CARRY FLAG has been set the operator ended the command, so return to DEBUG.
4EB5
INC HL 23
Advance Register Pair HL to the next byte, so entry continues through memory.
4EB6
LD (4060H),HL 22 60 40
Store the advanced pointer into 4060H, the memory-modify pointer in DBGSV$, so DEBUG and a repeat of the command both resume here.
4EB9
LOOP BACK to 4EA3H to show and offer the next byte.
Loop End

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.

4EBB
CP 66H FE 66
Compare the command character in Register A against 66H, the ASCII code for f. If Register A equals 66H, the Z FLAG is set.
4EBD
If the NZ FLAG has been set the command is not f, so JUMP to 4EDBH to try the next one.
4EBF
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the address at which the fill begins.
4EC2
RET Z C8
If the Z FLAG has been set no address was given, so there is nothing to fill; return to DEBUG.
4EC3
PUSH HL E5
Save the starting address while the ending address is collected.
4EC4
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the address at which the fill ends.
4EC7
EX (SP),HL E3
Exchange the top of the stack with Register Pair HL, so Register Pair HL holds the starting address again and the ending address is on the stack.
4EC8
POP BC C1
Recover the ending address into Register Pair BC, which is what the loop below compares against.
4EC9
RET Z C8
If the Z FLAG has been set no ending address was given; return to DEBUG.
4ECA
PUSH HL E5
Save the starting address while the fill byte is collected.
4ECB
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the byte value the range is to be filled with.
4ECE
LD E,L 5D
Copy the low byte of that value from Register L into Register E, which is where the store below takes it from.
4ECF
POP HL E1
Recover the starting address into Register Pair HL.
4ED0
RET Z C8
If the Z FLAG has been set no fill byte was given; return to DEBUG.
4ED1
XOR A AF
Set Register A to ZERO and clear all flags, in particular the CARRY flag, so the comparison below is exact.
4ED2
PUSH HL E5
Loop Start
Save the current address while it is compared against the end of the range.
4ED3
SBC HL,BC ED 42
Subtract the ending address in Register Pair BC from the current address in Register Pair HL. The CARRY flag is left set while the current address is still below the end.
4ED5
POP HL E1
Restore the current address, the comparison having only been needed for its flags.
4ED6
RET NC D0
If the NO CARRY FLAG has been set the current address has reached the end of the range, so the fill is complete; return to DEBUG.
4ED7
LD (HL),E 73
Store the fill byte from Register E into memory at the address in Register Pair HL.
4ED8
INC HL 23
Advance Register Pair HL to the next byte of the range.
4ED9
LOOP BACK to 4ED2H to fill the next byte.
Loop End

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.

4EDB
CP 6AH FE 6A
Compare the command character in Register A against 6AH, the ASCII code for j. If Register A equals 6AH, the Z FLAG is set.
4EDD
If the NZ FLAG has been set the command is not j, so JUMP to 4EE7H to try the next one.
4EDF
LD HL,(407BH) 2A 7B 40
Fetch the program counter of the program under test from 407BH, the last entry of the register snapshot SYS5/SYS DEBUG takes into DBGSV$ when it is entered.
4EE2
INC HL 23
Increment it by one, stepping the saved program counter over a single byte of the program under test.
4EE3
LD (407BH),HL 22 7B 40
Store the advanced program counter back into 407BH, so that when DEBUG resumes the program it restarts one byte further on. This is how a breakpoint opcode or a single instruction byte is skipped.
4EE6
RET C9
Return to DEBUG with the saved program counter advanced.

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.

4EE7
CP 6CH FE 6C
Compare the command character in Register A against 6CH, the ASCII code for l. If Register A equals 6CH, the Z FLAG is set.
4EE9
If the NZ FLAG has been set the command is not l, so JUMP to 4F2EH to try the next one.
4EEB
LD HL,(4060H) 2A 60 40
Fetch the memory-modify pointer that SYS5/SYS DEBUG keeps at 4060H in its save area DBGSV$, which is where its own m command and the memory display are currently positioned into Register Pair HL.
4EEE
INC HL 23
Advance Register Pair HL by one so that a repeated search starts past the byte the previous one found, rather than finding it again.
4EEF
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the address at which the search is to begin.
4EF2
LD (4060H),HL 22 60 40
Store that address into 4060H, the memory-modify pointer in DBGSV$.
4EF5
If the NZ FLAG has been set the operator typed the address, so JUMP to 4F05H and skip echoing it.
4EF7
PUSH AF F5
Save the flags from the parse, which the echo below would otherwise destroy.
4EF8
GOSUB to the display routine at 51D5H inside the SYS5/SYS DEBUG overlay, which shows Register Pair HL on the screen as four hexadecimal digits. The defaulted starting address is echoed.
4EFB
LD A,2CH 3E 2C
Load Register A with 2CH, the ASCII code for a comma.
4EFD
GOSUB to the ROM character-output routine at 0033H to display the comma, separating this value from the next on the command line.
4F00
POP AF F1
Restore the flags from the parse.
4F01
LD A,(4062H) 3A 62 40
Fetch the byte value used by the previous search from 4062H, a scratch byte of DBGSV$, so that repeating the command searches for the same value again.
4F04
LD L,A 6F
Move that value into Register L, where the parser would have left a newly typed one.
4F05
If the CARRY FLAG has been set the operator ended the command after the address, so JUMP to 4F12H to search for the previous value.
4F07
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the byte value to search for.
4F0A
If the Z FLAG has been set no value was typed, so JUMP to 4F12H to keep the previous one.
4F0C
LD A,L 7D
Copy the newly typed value from Register L into Register A.
4F0D
LD (4062H),A 32 62 40
Store it into 4062H so that a later repeat of the command searches for the same value.
4F10
JUMP to 4F16H to begin the search.
4F12
LD A,L 7D
Copy the value being searched for from Register L into Register A.
4F13
GOSUB to the display routine at 51DAH inside the SYS5/SYS DEBUG overlay, which shows Register A on the screen as two hexadecimal digits. The value carried over from the previous search is echoed so the operator can see what is being looked for.
4F16
LD HL,(4060H) 2A 60 40
Fetch the starting address from 4060H, the memory-modify pointer in DBGSV$, into Register Pair HL.
4F19
LD A,(4062H) 3A 62 40
Fetch the byte value being searched for from 4062H into Register A.
4F1C
LD BC,0000H 01 00 00
Clear Register Pair BC to zero, which the block-compare instruction below treats as a count of 65536 and so lets the search run through the whole of memory.
4F1F
CPIR ED B1
Compare, increment and repeat: scan forward from the address in Register Pair HL for a byte equal to Register A, stopping at the first match or when the count runs out.
4F21
RET NZ C0
If the NZ FLAG has been set the value was not found anywhere; return to DEBUG leaving the pointer alone.
4F22
DEC HL 2B
The value was found. Step Register Pair HL back onto the matching byte, which the block compare left it one past.
4F23
LD (4060H),HL 22 60 40
Store the address of the match into 4060H, the memory-modify pointer in DBGSV$, so DEBUG is positioned on it.
4F26
LD A,L 7D
Copy the low byte of the address into Register A.
4F27
AND 0C0H E6 C0
Round it down to a multiple of 40H, which is the start of the display line the match falls on.
4F29
LD L,A 6F
Move the rounded value back into Register L.
4F2A
LD (4063H),HL 22 63 40
Store the rounded address into 4063H, the memory-display window address in DBGSV$, so the screen shows the block containing the match.
4F2D
RET C9
Return to DEBUG with the display positioned on the byte that was found.

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.

4F2E
CP 6EH FE 6E
Compare the command character in Register A against 6EH, the ASCII code for n. If Register A equals 6EH, the Z FLAG is set.
4F30
If the NZ FLAG has been set the command is not n, so JUMP to 4F4FH to try the next one.
4F32
LD HL,(4060H) 2A 60 40
Fetch the memory-modify pointer that SYS5/SYS DEBUG keeps at 4060H in its save area DBGSV$, which is where its own m command and the memory display are currently positioned into Register Pair HL, which is expected to be sitting on the type byte of a load-module record.
4F35
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This lets the operator give an address other than the current one.
4F38
INC HL 23
Advance Register Pair HL past the record type byte to the length byte that follows it.
4F39
LD D,00H 16 00
Clear Register D, the high half of the length about to be worked out.
4F3B
LD E,(HL) 5E
Fetch the record length byte into Register E.
4F3C
LD A,E 7B
Copy it into Register A for testing.
4F3D
CP 03H FE 03
Compare the length against 03H. In an LDOS load module a length byte below three means the record is a long one, the count having wrapped, so 256 has to be added back.
4F3F
If the NO CARRY FLAG has been set the length is three or more and stands as written, so JUMP to 4F42H.
4F41
INC D 14
Increment Register D, adding 256 to the length to undo the wrap.
4F42
INC DE 13
Increment Register Pair DE so the length also covers the length byte itself.
4F43
ADD HL,DE 19
Add the length to Register Pair HL, so Register Pair HL now addresses the type byte of the following load-module record.
4F44
LD (4060H),HL 22 60 40
Store that address into 4060H, the memory-modify pointer in DBGSV$, positioning DEBUG on the next record.
4F47
LD A,L 7D
Copy the low byte of the address into Register A.
4F48
AND 0C0H E6 C0
Round it down to a multiple of 40H, the start of the display line the record falls on.
4F4A
LD L,A 6F
Move the rounded value back into Register L.
4F4B
LD (4063H),HL 22 63 40
Store the rounded address into 4063H, the memory-display window address in DBGSV$, so the screen shows the new record.
4F4E
RET C9
Return to DEBUG positioned on the next load-module record.

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.

4F4F
CP 71H FE 71
Compare the command character in Register A against 71H, the ASCII code for q. If Register A equals 71H, the Z FLAG is set.
4F51
If the NZ FLAG has been set the command is not q, so JUMP to 4F73H to try the next one.
4F53
LD A,1EH 3E 1E
Load Register A with 1EH, the video control code that erases from the cursor to the end of the line.
4F55
GOSUB to the ROM character-output routine at 0033H to send that control code to the video device.
4F58
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the port number.
4F5B
RET Z C8
If the Z FLAG has been set no port number was given; return to DEBUG.
4F5C
LD C,L 4D
Copy the port number from Register L into Register C, which is the register the Z80 port instructions address a port through.
4F5D
If the CARRY FLAG has been set the operator ended the command after the port number, so no value follows and this is a read; JUMP to 4F66H.
4F5F
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the value to be written to the port.
4F62
RET Z C8
If the Z FLAG has been set no value was given; return to DEBUG.
4F63
OUT (C),L ED 69
Send the low byte of the typed value, held in Register L, to the port whose number is in Register C.
4F65
RET C9
Return to DEBUG, the value having been written to the port.
4F66
LD A,3DH 3E 3D
Load Register A with 3DH, the ASCII code for an equals sign, which introduces the value read back.
4F68
GOSUB to the ROM character-output routine at 0033H to display it.
4F6B
IN A,(C) ED 78
Read a byte from the port whose number is in Register C into Register A.
4F6D
GOSUB to the display routine at 51DAH inside the SYS5/SYS DEBUG overlay, which shows Register A on the screen as two hexadecimal digits. The value read from the port is displayed.
4F70
JUMP to the single-key command reader at 5189H inside the SYS5/SYS DEBUG overlay, which waits for a keystroke before the display is disturbed and then returns to DEBUG on this routine's behalf.

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.

4F73
CP 74H FE 74
Compare the command character in Register A against 74H, the ASCII code for t. If Register A equals 74H, the Z FLAG is set.
4F75
If the NZ FLAG has been set the command is not t, so JUMP to 4FBBH to try the next one.
4F77
LD HL,(4060H) 2A 60 40
Fetch the memory-modify pointer that SYS5/SYS DEBUG keeps at 4060H in its save area DBGSV$, which is where its own m command and the memory display are currently positioned into Register Pair HL, to serve as the default address at which text entry begins.
4F7A
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the address at which to start entering text.
4F7D
LD (4060H),HL 22 60 40
Store that address into 4060H, the memory-modify pointer in DBGSV$.
4F80
RET C D8
If the CARRY FLAG has been set the operator ended the command; return to DEBUG.
4F81
If the NZ FLAG has been set the operator typed the address, so JUMP to 4F86H and skip echoing it.
4F83
GOSUB to the display routine at 51D5H inside the SYS5/SYS DEBUG overlay, which shows Register Pair HL on the screen as four hexadecimal digits. The defaulted address is echoed.
4F86
LD A,1EH 3E 1E
Load Register A with 1EH, the video control code that erases from the cursor to the end of the line.
4F88
GOSUB to the ROM character-output routine at 0033H to send that control code to the video device.
4F8B
Loop Start
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.
4F8E
LD A,(HL) 7E
Fetch the byte currently at the address in Register Pair HL into Register A, so the operator can see what is being replaced.
4F8F
AND 7FH E6 7F
Strip bit 7, folding a graphics or inverse byte down into the printable range for the test below.
4F91
CP 20H FE 20
Compare the byte against 20H, a space, the lowest printable character.
4F93
If the CARRY FLAG has been set the byte is a control code and cannot be shown, so JUMP to 4F99H to display a substitute.
4F95
CP 0C0H FE C0
Compare the byte against 0C0H, above which the codes are graphics rather than text.
4F97
If the CARRY FLAG has been set the byte is printable and may be shown as it stands, so JUMP to 4F9BH.
4F99
LD A,2EH 3E 2E
Load Register A with 2EH, the ASCII code for a full stop, which stands in for any byte that cannot be displayed.
4F9B
GOSUB to the ROM character-output routine at 0033H to display the byte or its substitute.
4F9E
LD A,2DH 3E 2D
Load Register A with 2DH, the ASCII code for a hyphen, the prompt that separates the old character from the new.
4FA0
GOSUB to the ROM character-output routine at 0033H to display that prompt.
4FA3
PUSH HL E5
Save the memory pointer while the keyboard is read.
4FA4
LD HL,5196H 21 96 51
Load Register Pair HL with 5196H, an instruction inside the single-key command reader of the SYS5/SYS DEBUG overlay.
4FA7
LD (HL),6FH 36 6F
Self-Modifying Code
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.
4FA9
GOSUB to the single-key command reader at 5189H inside the SYS5/SYS DEBUG overlay, which waits for one keystroke and returns it in Register A with the CARRY flag set if the operator broke out. With the reader relaxed by the patch above, this returns the character the operator wants stored.
4FAC
LD (HL),0EFH 36 EF
Self-Modifying Code
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.
4FAE
POP HL E1
Recover the memory pointer.
4FAF
RET C D8
If the CARRY FLAG has been set the operator ended the command; return to DEBUG.
4FB0
CP 20H FE 20
Compare the character typed against 20H, a space, which is the way of leaving a byte unchanged.
4FB2
If the Z FLAG has been set a space was typed, so JUMP to 4FB5H and leave the byte as it stands.
4FB4
LD (HL),A 77
Store the character typed into memory at the address in Register Pair HL.
4FB5
INC HL 23
Advance Register Pair HL to the next byte.
4FB6
LD (4060H),HL 22 60 40
Store the advanced pointer into 4060H, the memory-modify pointer in DBGSV$.
4FB9
LOOP BACK to 4F8BH to show and offer the next character.
Loop End

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.

4FBB
CP 76H FE 76
Compare the command character in Register A against 76H, the ASCII code for v. If Register A equals 76H, the Z FLAG is set.
4FBD
If the NZ FLAG has been set the command is not v, so JUMP to 5016H to try the next one.
4FBF
LD HL,(4063H) 2A 63 40
Fetch the memory-display window address that SYS5/SYS DEBUG keeps at 4063H in its save area DBGSV$, which fixes what the screen is showing into Register Pair HL, to serve as the default address of the first block.
4FC2
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the address of the first block.
4FC5
LD (4063H),HL 22 63 40
Store it into 4063H, the memory-display window address in DBGSV$.
4FC8
If the NZ FLAG has been set the operator typed the address, so JUMP to 4FD4H and skip echoing it.
4FCA
PUSH AF F5
Save the flags from the parse.
4FCB
GOSUB to the display routine at 51D5H inside the SYS5/SYS DEBUG overlay, which shows Register Pair HL on the screen as four hexadecimal digits. The defaulted first address is echoed.
4FCE
LD A,2CH 3E 2C
Load Register A with 2CH, the ASCII code for a comma.
4FD0
GOSUB to the ROM character-output routine at 0033H to display the comma, separating this value from the next on the command line.
4FD3
POP AF F1
Restore the flags from the parse.
4FD4
If the CARRY FLAG has been set the operator ended the command, so JUMP to 4FEBH to use the default length.
4FD6
LD HL,(4060H) 2A 60 40
Fetch the memory-modify pointer that SYS5/SYS DEBUG keeps at 4060H in its save area DBGSV$, which is where its own m command and the memory display are currently positioned into Register Pair HL, to serve as the default address of the second block.
4FD9
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the address of the second block.
4FDC
LD (4060H),HL 22 60 40
Store it into 4060H, the memory-modify pointer in DBGSV$.
4FDF
If the NZ FLAG has been set the operator typed the address, so JUMP to 4FEBH.
4FE1
PUSH AF F5
Save the flags from the parse.
4FE2
GOSUB to the display routine at 51D5H inside the SYS5/SYS DEBUG overlay, which shows Register Pair HL on the screen as four hexadecimal digits. The defaulted second address is echoed.
4FE5
LD A,2CH 3E 2C
Load Register A with 2CH, the ASCII code for a comma.
4FE7
GOSUB to the ROM character-output routine at 0033H to display the comma, separating this value from the next on the command line.
4FEA
POP AF F1
Restore the flags from the parse.
4FEB
LD HL,0000H 21 00 00
Clear Register Pair HL to zero, the length the comparison uses when the operator gives none, which the loop below treats as 65536 bytes.
4FEE
If the CARRY FLAG has been set the operator ended the command, so JUMP to 4FFAH to use that default.
4FF0
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the number of bytes to compare.
4FF3
If the NZ FLAG has been set a length was typed, so JUMP to 4FFAH.
4FF5
PUSH HL E5
Save the length while it is echoed.
4FF6
GOSUB to the display routine at 51D5H inside the SYS5/SYS DEBUG overlay, which shows Register Pair HL on the screen as four hexadecimal digits. The defaulted length is echoed.
4FF9
POP HL E1
Recover the length.
4FFA
LD B,H 44
Copy the high byte of the length from Register H into Register B.
4FFB
LD C,L 4D
Copy the low byte from Register L into Register C, so Register Pair BC counts the bytes to compare.
4FFC
LD HL,(4063H) 2A 63 40
Fetch the address of the first block from 4063H into Register Pair HL.
4FFF
LD DE,(4060H) ED 5B 60 40
Fetch the address of the second block from 4060H into Register Pair DE.
5003
LD A,(DE) 1A
Loop Start
Fetch the next byte of the second block, addressed by Register Pair DE, into Register A.
5004
CP (HL) BE
Compare it against the corresponding byte of the first block, addressed by Register Pair HL. If they are equal, the Z FLAG is set.
5005
If the NZ FLAG has been set the two blocks differ here, so JUMP to 500EH to leave both pointers on the difference.
5007
INC DE 13
Advance Register Pair DE to the next byte of the second block.
5008
INC HL 23
Advance Register Pair HL to the next byte of the first block.
5009
DEC BC 0B
Decrement Register Pair BC, the count of bytes still to compare.
500A
LD A,B 78
Copy the high byte of the remaining count into Register A.
500B
OR C B1
OR it with the low byte. The Z FLAG is set only when both halves have reached zero.
500C
If the NZ FLAG has been set there are bytes left to compare, so LOOP BACK to 5003H.
Loop End
500E
LD (4060H),DE ED 53 60 40
Store the second-block pointer into 4060H, the memory-modify pointer in DBGSV$. Whether the comparison ran out or stopped at a difference, DEBUG is left positioned on the byte concerned.
5012
LD (4063H),HL 22 63 40
Store the first-block pointer into 4063H, the memory-display window address in DBGSV$, so the screen shows the same place in the other block.
5015
RET C9
Return to DEBUG with both pointers left where the comparison ended.

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.

5016
CP 77H FE 77
Compare the command character in Register A against 77H, the ASCII code for w. If Register A equals 77H, the Z FLAG is set.
5018
If the NZ FLAG has been set the command is not w, so JUMP to 506CH to try the disk and printer commands.
501A
LD HL,(4060H) 2A 60 40
Fetch the memory-modify pointer that SYS5/SYS DEBUG keeps at 4060H in its save area DBGSV$, which is where its own m command and the memory display are currently positioned into Register Pair HL.
501D
INC HL 23
Advance Register Pair HL by one byte, so a repeated search starts past the pair the previous one found.
501E
INC HL 23
Advance Register Pair HL by a second byte, clearing the whole of the two-byte value that was matched last time.
501F
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the address at which the search is to begin.
5022
LD (4060H),HL 22 60 40
Store that address into 4060H, the memory-modify pointer in DBGSV$.
5025
If the NZ FLAG has been set the operator typed the address, so JUMP to 5039H and skip echoing it.
5027
PUSH AF F5
Save the flags from the parse.
5028
GOSUB to the display routine at 51D5H inside the SYS5/SYS DEBUG overlay, which shows Register Pair HL on the screen as four hexadecimal digits. The defaulted starting address is echoed.
502B
LD A,2CH 3E 2C
Load Register A with 2CH, the ASCII code for a comma.
502D
GOSUB to the ROM character-output routine at 0033H to display the comma, separating this value from the next on the command line.
5030
POP AF F1
Restore the flags from the parse.
5031
LD A,(4062H) 3A 62 40
Fetch the low byte of the value used by the previous search from 4062H, a scratch byte of DBGSV$.
5034
LD L,A 6F
Move it into Register L.
5035
LD A,(405FH) 3A 5F 40
Fetch the high byte of the value used by the previous search from 405FH, another scratch byte of DBGSV$.
5038
LD H,A 67
Move it into Register H, so Register Pair HL holds the whole of the previous search value.
5039
If the CARRY FLAG has been set the operator ended the command, so JUMP to 504AH to search for the previous value.
503B
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the two-byte value to search for.
503E
If the Z FLAG has been set no value was typed, so JUMP to 504AH to keep the previous one.
5040
LD A,L 7D
Copy the low byte of the newly typed value from Register L into Register A.
5041
LD (4062H),A 32 62 40
Store it into 4062H for a later repeat of the command.
5044
LD A,H 7C
Copy the high byte from Register H into Register A.
5045
LD (405FH),A 32 5F 40
Store it into 405FH for a later repeat of the command.
5048
JUMP to 504DH to begin the search.
504A
GOSUB to the display routine at 51D5H inside the SYS5/SYS DEBUG overlay, which shows Register Pair HL on the screen as four hexadecimal digits. The value carried over from the previous search is echoed.
504D
LD HL,(4060H) 2A 60 40
Loop Start
Fetch the starting address from 4060H, the memory-modify pointer in DBGSV$, into Register Pair HL.
5050
LD BC,0000H 01 00 00
Clear Register Pair BC to zero, which the block-compare instruction treats as a count of 65536 so the search covers the whole of memory.
5053
LD A,(4062H) 3A 62 40
Fetch the low byte of the value being searched for from 4062H into Register A.
5056
CPIR ED B1
Compare, increment and repeat: scan forward for a byte equal to Register A, stopping at the first match or when the count runs out.
5058
RET PO E0
If the Parity Odd condition holds the count ran out without a match, so return to DEBUG having found nothing.
5059
LD A,(405FH) 3A 5F 40
A byte matching the low half was found. Fetch the high byte of the value being searched for from 405FH into Register A.
505C
CP (HL) BE
Compare it against the byte that follows the match, addressed by Register Pair HL. If they are equal the whole two-byte value has been found.
505D
If the NZ FLAG has been set only the low half matched, so LOOP BACK to 5053H to carry the search on from here.
Loop End
505F
DEC HL 2B
Both halves matched. Step Register Pair HL back one byte.
5060
DEC HL 2B
Step Register Pair HL back a second byte, so it addresses the first of the two matching bytes.
5061
LD (4060H),HL 22 60 40
Store the address of the match into 4060H, the memory-modify pointer in DBGSV$.
5064
LD A,L 7D
Copy the low byte of the address into Register A.
5065
AND 0C0H E6 C0
Round it down to a multiple of 40H, the start of the display line the match falls on.
5067
LD L,A 6F
Move the rounded value back into Register L.
5068
LD (4063H),HL 22 63 40
Store the rounded address into 4063H, the memory-display window address in DBGSV$, so the screen shows the match.
506B
RET C9
Return to DEBUG positioned on the two-byte value that was found.

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.

506C
SUB 30H D6 30
The command is none of the letter commands. Subtract 30H, the ASCII code for 0, so that a digit key becomes its own value.
506E
CP 08H FE 08
Compare the result against 08H. LDOS supports drives 0 through 7, so a value below eight names a drive.
5070
If the NO CARRY FLAG has been set the character is not a drive digit, so JUMP to 50D5H to try the printer command.
5072
LD C,A 4F
Move the drive number into Register C, where the resident drive routines expect it.
5073
GOSUB to the resident GETDCT routine at 478FH, which sets Register Pair IY to the Drive Code Table entry of the drive in Register C.
5076
LD A,(IY+07H) FD 7E 07
Fetch Drive Code Table byte 7 of that drive into Register A. Its bits 7-5 hold the head count and its bits 4-0 the highest sector number on a track.
5079
AND 0E0H E6 E0
Keep only bits 7-5, the head count field.
507B
RLCA 07
Rotate Register A left circularly; three of these instructions together bring that field down into bits 2-0.
507C
RLCA 07
Rotate Register A left circularly a second time.
507D
RLCA 07
Rotate Register A left circularly a third time, so the head count now occupies the low bits of Register A.
507E
INC A 3C
Increment Register A so it holds the true number of heads rather than that count minus one.
507F
LD B,A 47
Move the head count into Register B, the counter of the multiplication below.
5080
LD A,(IY+07H) FD 7E 07
Fetch Drive Code Table byte 7 again into Register A.
5083
AND 1FH E6 1F
Keep only bits 4-0, the highest sector number on a track.
5085
INC A 3C
Increment Register A so it holds the true number of sectors on a track.
5086
LD H,A 67
Move the sectors-per-track figure into Register H, the addend of the multiplication below.
5087
XOR A AF
Set Register A to ZERO and clear all flags, to accumulate the product.
5088
ADD A,H 84
Loop Start
Add the sectors-per-track figure in Register H to the running total in Register A.
5089
DECrement Register B and loop back to 5088H if it has not reached zero, so that Register A ends up holding sectors per track multiplied by the head count.
Loop End
508B
BIT 5,(IY+04H) FD CB 04 6E
Test bit 5 of Drive Code Table byte 4 of this drive, which records whether the drive is double sided.
508F
If the Z FLAG has been set the drive is single sided and the figure stands, so JUMP to 5092H.
5091
ADD A,A 87
The drive is double sided, so a cylinder holds twice as many sectors. Add Register A to itself.
5092
LD (405FH),A 32 5F 40
Store the sectors-per-cylinder figure into 405FH, a scratch byte of DBGSV$, where the loop below reads it to know when to roll onto the next cylinder.
5095
LD A,1EH 3E 1E
Load Register A with 1EH, the video control code that erases from the cursor to the end of the line.
5097
GOSUB to the ROM character-output routine at 0033H to send that control code to the video device.
509A
GOSUB to the single-key command reader at 5189H inside the SYS5/SYS DEBUG overlay, which waits for one keystroke and returns it in Register A with the CARRY flag set if the operator broke out. This takes the separator the operator types after the drive number.
509D
RET C D8
If the CARRY FLAG has been set the operator ended the command; return to DEBUG.
509E
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the cylinder number.
50A1
RET C D8
If the CARRY FLAG has been set the operator ended the command; return to DEBUG.
50A2
LD D,L 55
Move the cylinder number from Register L into Register D, where the resident sector routines expect it.
50A3
If the NZ FLAG has been set a cylinder number was typed, so JUMP to 50A8H.
50A5
LD D,(IY+09H) FD 56 09
No cylinder was given, so take Drive Code Table byte 9 of this drive, the directory cylinder, as the default.
50A8
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the sector number.
50AB
LD E,L 5D
Move the sector number from Register L into Register E, where the resident sector routines expect it.
50AC
LD A,01H 3E 01
Load Register A with 01H, the number of sectors to transfer when the operator names a particular one.
50AE
If the NZ FLAG has been set a sector number was typed, so JUMP to 50B5H with a count of one.
50B0
LD E,00H 1E 00
No sector was given, so clear Register E to start at sector 0 of the cylinder.
50B2
LD A,(405FH) 3A 5F 40
Fetch the sectors-per-cylinder figure from 405FH into Register A, so that a whole cylinder is transferred.
50B5
LD (4062H),A 32 62 40
Store the number of sectors to transfer into 4062H, a scratch byte of DBGSV$.
50B8
RET C D8
If the CARRY FLAG has been set the operator ended the command; return to DEBUG.
50B9
GOSUB to the single-key command reader at 5189H inside the SYS5/SYS DEBUG overlay, which waits for one keystroke and returns it in Register A with the CARRY flag set if the operator broke out. This takes the separator before the operation letter.
50BC
RET C D8
If the CARRY FLAG has been set the operator ended the command; return to DEBUG.
50BD
LD B,A 47
Move the operation letter into Register B, where it is held for the whole of the transfer loop.
50BE
GOSUB to the single-key command reader at 5189H inside the SYS5/SYS DEBUG overlay, which waits for one keystroke and returns it in Register A with the CARRY flag set if the operator broke out. This takes the separator before the buffer address.
50C1
RET C D8
If the CARRY FLAG has been set the operator ended the command; return to DEBUG.
50C2
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the address of the buffer the sectors are read into or written from.
50C5
PUSH HL E5
Save the buffer address, which the loop below advances and the ending code recovers.
50C6
If the CARRY FLAG has been set the operator ended the command, so JUMP to 50D7H to begin the transfer.
50C8
PUSH HL E5
Save the buffer address again while a further parameter is collected.
50C9
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects an explicit count of sectors, overriding the one worked out above.
50CC
LD A,L 7D
Copy the low byte of that count from Register L into Register A.
50CD
POP HL E1
Recover the buffer address.
50CE
If the Z FLAG has been set no count was typed, so JUMP to 50D7H and keep the count already worked out.
50D0
LD (4062H),A 32 62 40
Store the explicit count into 4062H, replacing the earlier figure.
50D3
JUMP to 50D7H to begin the transfer.
50D5
The character was not a drive digit. JUMP to 513EH to test it against the printer command.
50D7
LD A,B 78
Loop Start
Copy the operation letter from Register B into Register A.
50D8
CP 72H FE 72
Compare it against 72H, the ASCII code for r, which reads sectors from the disk.
50DA
If the Z FLAG has been set the operation is a read, so JUMP to 510CH.
50DC
CP 77H FE 77
Compare the operation letter against 77H, the ASCII code for w, which writes sectors to the disk.
50DE
If the Z FLAG has been set the operation is a write, so JUMP to 5117H.
50E0
CP 2AH FE 2A
Compare the operation letter against 2AH, the ASCII code for *, which writes a sector with a deleted-data address mark as the directory cylinder is recorded.
50E2
If the Z FLAG has been set the operation is a protected write, so JUMP to 511EH.
50E4
INC H 24
Advance Register H by one, moving the buffer address on by 256 bytes, the size of one sector.
50E5
INC E 1C
Advance Register E to the next sector number.
50E6
LD A,(405FH) 3A 5F 40
Fetch the sectors-per-cylinder figure from 405FH into Register A.
50E9
DEC A 3D
Decrement it to give the highest sector number on the cylinder.
50EA
CP E BB
Compare that against the sector number now in Register E.
50EB
If the NO CARRY FLAG has been set the sector number is still within the cylinder, so JUMP to 50F0H.
50ED
LD E,00H 1E 00
The cylinder has been exhausted. Reset Register E to sector 0.
50EF
INC D 14
Advance Register D to the next cylinder.
50F0
LD A,(4062H) 3A 62 40
Fetch the count of sectors still to transfer from 4062H into Register A.
50F3
DEC A 3D
Decrement it.
50F4
LD (4062H),A 32 62 40
Store the reduced count back into 4062H.
50F7
If the NZ FLAG has been set there are sectors left, so LOOP BACK to 50D7H to transfer the next one.
Loop End
50F9
POP HL E1
Recover the buffer address as it stood before the transfer began.
50FA
LD A,B 78
Copy the operation letter from Register B into Register A.
50FB
CP 72H FE 72
Compare it against 72H, the ASCII code for r.
50FD
RET NZ C0
If the NZ FLAG has been set the operation was not a read, so there is nothing new to look at; return to DEBUG.
50FE
LD L,00H 2E 00
The operation was a read. Clear Register L so that Register Pair HL addresses the start of the page the sectors were read into.
5100
LD (4063H),HL 22 63 40
Store that address into 4063H, the memory-display window address in DBGSV$.
5103
LD (4060H),HL 22 60 40
Store it into 4060H as well, the memory-modify pointer in DBGSV$, so both the display and the editing position sit on the data just read.
5106
LD A,73H 3E 73
Load Register A with 73H, the value that selects the full-memory screen layout.
5108
LD (405EH),A 32 5E 40
Store it into 405EH, the screen-layout flag in DBGSV$, so that DEBUG comes back showing the sector just read rather than the register display.
510B
RET C9
Return to DEBUG with the sector displayed.
510C
GOSUB to the resident RDSECT routine at 4777H to read the sector named by Register D and Register E from the drive in Register C into the buffer addressed by Register Pair HL.
510F
If the Z FLAG has been set the read succeeded, so JUMP to 50E4H to move on to the next sector.
5111
CP 06H FE 06
Compare the status in Register A against 06H, which is not treated as a failure here.
5113
If the Z FLAG has been set the status is acceptable, so JUMP to 50E4H to move on to the next sector.
5115
JUMP to 5123H to report the error to the operator.
5117
GOSUB to the resident WRSECT routine at 4763H to write the buffer addressed by Register Pair HL to the sector named by Register D and Register E on the drive in Register C.
511A
If the Z FLAG has been set the write succeeded, so JUMP to 50E4H to move on to the next sector.
511C
JUMP to 5123H to report the error to the operator.
511E
GOSUB to the resident WRPROT routine at 4768H, which writes the sector with a deleted-data address mark, the way every sector of the directory cylinder is recorded.
5121
If the Z FLAG has been set the write succeeded, so JUMP to 50E4H to move on to the next sector.
5123
PUSH DE D5
Save the cylinder and sector numbers across the error report.
5124
PUSH AF F5
Save the error status returned by the resident routine.
5125
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.
5128
LD A,2AH 3E 2A
Load Register A with 2AH, the ASCII code for *, which brackets the error code.
512A
GOSUB to the ROM character-output routine at 0033H to display it.
512D
POP AF F1
Recover the error status.
512E
GOSUB to the display routine at 51DAH inside the SYS5/SYS DEBUG overlay, which shows Register A on the screen as two hexadecimal digits. The error code returned by the resident disk routine is displayed.
5131
LD A,2AH 3E 2A
Load Register A with 2AH, the ASCII code for *, closing the bracket around the error code.
5133
GOSUB to the ROM character-output routine at 0033H to display it.
5136
GOSUB to the single-key command reader at 5189H inside the SYS5/SYS DEBUG overlay, which waits for one keystroke and returns it in Register A with the CARRY flag set if the operator broke out. The operator decides whether to carry on after the error.
5139
POP DE D1
Recover the cylinder and sector numbers.
513A
If the NO CARRY FLAG has been set the operator chose to carry on, so JUMP to 50E4H to move on to the next sector.
513C
JUMP to 50F9H to abandon the transfer.

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.

513E
CP 40H FE 40
Compare the command character in Register A against 40H, the ASCII code for @, the last of the extended commands.
5140
RET NZ C0
If the NZ FLAG has been set the character is none of the extended commands at all; return to DEBUG, which reports it as unrecognised.
5141
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the address at which the printed dump is to begin.
5144
RET Z C8
If the Z FLAG has been set no starting address was given; return to DEBUG.
5145
PUSH HL E5
Save the starting address while the ending address is collected.
5146
GOSUB to the hexadecimal parameter parser at 51A4H inside the SYS5/SYS DEBUG overlay, which collects hexadecimal digits from the keyboard into Register Pair HL. It comes back with the Z FLAG set when no digits at all were given, so the caller can supply a default, and the CARRY flag set when the operator ended the command. This collects the address at which the printed dump is to end.
5149
EX (SP),HL E3
Exchange the top of the stack with Register Pair HL, so Register Pair HL holds the starting address again and the ending address is on the stack.
514A
POP BC C1
Recover the ending address into Register Pair BC, which the loop below compares against.
514B
RET Z C8
If the Z FLAG has been set no ending address was given; return to DEBUG.
514C
LD A,L 7D
Copy the low byte of the starting address into Register A.
514D
AND 0F0H E6 F0
Round it down to a multiple of 10H, so the dump begins on a tidy sixteen-byte boundary.
514F
LD L,A 6F
Move the rounded value back into Register L.
5150
LD A,0DH 3E 0D
Load Register A with 0DH, a carriage return.
5152
GOSUB to the ROM printer-output routine at 003BH to send it, starting a fresh line on the printer.
5155
GOSUB to the ROM printer-output routine at 003BH again, leaving a blank line above the dump.
5158
PUSH HL E5
Loop Start
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.
5159
LD A,H 7C
Copy the high byte of the address into Register A.
515A
RRA 1F
Rotate Register A right; four of these instructions together bring the upper nibble down into the low four bits.
515B
RRA 1F
Rotate Register A right a second time.
515C
RRA 1F
Rotate Register A right a third time.
515D
RRA 1F
Rotate Register A right a fourth time, so the upper nibble of the high byte now occupies the low four bits.
515E
AND 0FH E6 0F
Keep only those four bits, discarding whatever the rotates brought down from above.
5160
ADD A,90H C6 90
Add 90H. This begins the standard sequence that turns a nibble into its hexadecimal character without a lookup table.
5162
DAA 27
Decimal adjust. The value becomes 90H to 99H for nibbles 0 to 9 and carries for nibbles A to F.
5163
ADC A,40H CE 40
Add 40H with carry, which lands digits on 30H to 39H and letters on 41H to 46H.
5165
DAA 27
Decimal adjust again, completing the conversion of the nibble to its ASCII hexadecimal character.
5166
GOSUB to the ROM printer-output routine at 003BH to print that character.
5169
LD A,H 7C
Copy the high byte of the address into Register A again for its lower nibble.
516A
AND 0FH E6 0F
Keep only the low four bits.
516C
ADD A,90H C6 90
Add 90H, beginning the nibble-to-character conversion.
516E
DAA 27
Decimal adjust.
516F
ADC A,40H CE 40
Add 40H with carry.
5171
DAA 27
Decimal adjust, completing the conversion.
5172
GOSUB to the ROM printer-output routine at 003BH to print that character.
5175
LD A,L 7D
Copy the low byte of the address into Register A.
5176
RRA 1F
Rotate Register A right; four of these instructions together bring its upper nibble down into the low four bits.
5177
RRA 1F
Rotate Register A right a second time.
5178
RRA 1F
Rotate Register A right a third time.
5179
RRA 1F
Rotate Register A right a fourth time.
517A
AND 0FH E6 0F
Keep only the low four bits.
517C
ADD A,90H C6 90
Add 90H, beginning the nibble-to-character conversion.
517E
DAA 27
Decimal adjust.
517F
ADC A,40H CE 40
Add 40H with carry.
5181
DAA 27
Decimal adjust, completing the conversion.
5182
GOSUB to the ROM printer-output routine at 003BH to print that character.
5185
LD A,L 7D
Copy the low byte of the address into Register A again for its lower nibble.
5186
AND 0FH E6 0F
Keep only the low four bits.
5188
ADD A,90H C6 90
Add 90H, beginning the nibble-to-character conversion.
518A
DAA 27
Decimal adjust.
518B
ADC A,40H CE 40
Add 40H with carry.
518D
DAA 27
Decimal adjust, completing the conversion.
518E
GOSUB to the ROM printer-output routine at 003BH to print the fourth and last digit of the address.
5191
LD A,20H 3E 20
Load Register A with 20H, a space.
5193
GOSUB to the ROM printer-output routine at 003BH to print it, separating the address from the data.
5196
GOSUB to the ROM printer-output routine at 003BH to print a second space.
5199
JUMP to 519DH to print the sixteen data bytes of this line.
519B
LOOP BACK to 5158H to print the next line of the dump.
Loop End
519D
LD A,(HL) 7E
Loop Start
Fetch the next byte of the dump, addressed by Register Pair HL, into Register A.
519E
RRA 1F
Rotate Register A right; four of these instructions together bring its upper nibble down into the low four bits.
519F
RRA 1F
Rotate Register A right a second time.
51A0
RRA 1F
Rotate Register A right a third time.
51A1
RRA 1F
Rotate Register A right a fourth time.
51A2
AND 0FH E6 0F
Keep only the low four bits.
51A4
ADD A,90H C6 90
Add 90H, beginning the nibble-to-character conversion.
51A6
DAA 27
Decimal adjust.
51A7
ADC A,40H CE 40
Add 40H with carry.
51A9
DAA 27
Decimal adjust, completing the conversion.
51AA
GOSUB to the ROM printer-output routine at 003BH to print the upper digit of the byte.
51AD
LD A,(HL) 7E
Fetch the same byte again, addressed by Register Pair HL, into Register A.
51AE
AND 0FH E6 0F
Keep only its low four bits.
51B0
ADD A,90H C6 90
Add 90H, beginning the nibble-to-character conversion.
51B2
DAA 27
Decimal adjust.
51B3
ADC A,40H CE 40
Add 40H with carry.
51B5
DAA 27
Decimal adjust, completing the conversion.
51B6
GOSUB to the ROM printer-output routine at 003BH to print the lower digit of the byte.
51B9
LD A,20H 3E 20
Load Register A with 20H, a space.
51BB
GOSUB to the ROM printer-output routine at 003BH to print it between one byte and the next.
51BE
INC HL 23
Advance Register Pair HL to the next byte of the dump.
51BF
LD A,L 7D
Copy the low byte of the address into Register A.
51C0
AND 0FH E6 0F
Keep only its low four bits, which count the position within the sixteen-byte line.
51C2
If the Z FLAG has been set all sixteen bytes have been printed, so JUMP to 51CDH to print them again as characters.
51C4
AND 03H E6 03
Keep only the low two bits of the position, which are zero at every fourth byte.
51C6
LD A,20H 3E 20
Load Register A with 20H, a space, without disturbing the flags the test above produced.
51C8
If the Z FLAG has been set this is a four-byte boundary, so GOSUB to the ROM printer-output routine at 003BH to print an extra space and group the bytes in fours.
51CB
LOOP BACK to 519DH to print the next byte of the line.
Loop End
51CD
LD A,20H 3E 20
Load Register A with 20H, a space.
51CF
GOSUB to the ROM printer-output routine at 003BH to print it, separating the hexadecimal from the characters.
51D2
POP HL E1
Recover the address of the start of this line, saved at 5158H, so the same sixteen bytes can be shown as characters.
51D3
LD A,(HL) 7E
Loop Start
Fetch the next byte of the line, addressed by Register Pair HL, into Register A.
51D4
CP 20H FE 20
Compare it against 20H, a space, the lowest printable character.
51D6
If the CARRY FLAG has been set the byte is a control code and cannot be printed, so JUMP to 51DCH to print a substitute.
51D8
CP 80H FE 80
Compare the byte against 80H, above which the codes are graphics rather than text.
51DA
If the CARRY FLAG has been set the byte is printable and may be sent as it stands, so JUMP to 51DEH.
51DC
LD A,2EH 3E 2E
Load Register A with 2EH, the ASCII code for a full stop, which stands in for any byte that cannot be printed.
51DE
GOSUB to the ROM printer-output routine at 003BH to print the character or its substitute.
51E1
INC HL 23
Advance Register Pair HL to the next byte of the line.
51E2
LD A,L 7D
Copy the low byte of the address into Register A.
51E3
AND 0FH E6 0F
Keep only its low four bits, which count the position within the sixteen-byte line.
51E5
If the NZ FLAG has been set the line is not finished, so LOOP BACK to 51D3H to print the next character.
Loop End
51E7
LD A,0DH 3E 0D
Load Register A with 0DH, a carriage return.
51E9
GOSUB to the ROM printer-output routine at 003BH to end the line.
51EC
PUSH HL E5
Save the address of the next line while it is compared against the end of the range.
51ED
XOR A AF
Set Register A to ZERO and clear all flags, in particular the CARRY flag, so the comparison below is exact.
51EE
SBC HL,BC ED 42
Subtract the ending address in Register Pair BC from the current address in Register Pair HL. The CARRY flag is left set while the dump has not yet reached the end of the range.
51F0
POP HL E1
Restore the current address, the comparison having only been needed for its flags.
51F1
If the CARRY FLAG has been set there is more of the range to print, so JUMP to 519BH, which loops back for another line.
51F3
LD A,0DH 3E 0D
The dump is complete. Load Register A with 0DH, a carriage return.
51F5
GOSUB to the ROM printer-output routine at 003BH to send it.
51F8
GOSUB to the ROM printer-output routine at 003BH to send a second, leaving a blank line below the dump.
51FB
JUMP to the ROM printer-output routine at 003BH to send a third carriage return. That routine returns directly to DEBUG on this routine's behalf.