TRS-80 DOS - VTOS v4.0 - Disassembled
Page Index
Overview
VTOS v4.0 (Virtual Technology Operating System) was developed by Randolph Cook and published by Virtual Technology Inc. It is a third-party replacement DOS for the TRS-80 Model I, designed to provide enhanced capabilities over the standard TRSDOS. VTOS uses an overlay-based architecture with a resident core (SYS0/SYS) and swappable overlays (SYS1 through SYS10) loaded via an RST 28H overlay loader mechanism.
Unlike TRSDOS, which stores system files without directory entries and encodes their disk locations in the HIT table, VTOS maintains standard directory entries for its system files. The system disk contains the core operating system files (BOOT/SYS, SYS0-SYS10, CONFIG/SYS, DIR/SYS), device drivers (KSM/DVR, PR/DVR, RS232/DVR), utilities (BACKUP/CMD, FORMAT/CMD, KSR/CMD, PATCH/CMD, VTCOMM/CMD), BASIC support files (BASIC/CMD, BASIC/KSM, BASIC/OVN, BASIC/OVX), and JCL batch files (MAKE/JCL, SYSTEM/JCL).
The resident DOS core at SYS0/SYS occupies memory from 3C00H through approximately 50F8H and provides all fundamental file I/O, disk access, keyboard input, clock/date display, overlay loading, and system initialization services. SYS0/SYS employs several notable techniques including self-modifying code for write verify control, synthetic stack frames for automatic register preservation during FCB validation, and an RST 28H-based overlay loader that swaps SYS overlays into the overlay region as needed.
BOOT/SYS
Boot sector and initial loader for the VTOS operating system. This code resides in the first sector of the disk and is responsible for loading the resident DOS core (SYS0/SYS) into memory and transferring control to the cold boot initialization entry point.
A disassembly of BOOT/SYS can be found here.
SYS0/SYS - Resident DOS Core
The resident DOS core, loaded into memory at 3C00H by BOOT/SYS. SYS0/SYS remains in memory at all times and provides all fundamental operating system services. Major functional areas include:
| Address Range | Function |
|---|---|
| 3C00H-3CFFH | RST vector table and core entry points |
| 3D00H-44FFH | Disk I/O primitives, directory management, sector read/write, GAT/HIT processing |
| 4500H-483DH | File control block management, buffer handling, space allocation |
| 483FH-4A11H | Record-level I/O (read/write records, byte-level file access), FCB validation with synthetic stack frames, sector write with self-modifying verify flag |
| 4A12H-4ABBH | File space allocation, extent chain walking, overlay allocation |
| 4ABCH-4B6AH | Extent allocation, sector mapping, directory read/write, track cache |
| 4B6CH-4BA8H | Math routines: 8-bit multiply, 8-bit divide, 24-bit multiply |
| 4BA9H-4BBBH | 16-bit divide |
| 4BBCH-4C27H | Keyboard input and character processing |
| 4C28H-4CA8H | RST 28H overlay loader |
| 4CA9H-4CF7H | Overlay error display, load and execute, program loader entry |
| 4CF8H-4DAAH | Program load core, sector read helper |
| 4DABH-4DFFH | Clock display, date display, hex display |
| 4E00H-4FEDH | Cold boot entry, initialization, date/calendar, CONFIG/SYS processing, DOS ready jump |
| 4FEEH-50F8H | System startup finalization, date/day/month name strings, patch routine, drive activity timeout |
A disassembly of SYS0/SYS can be found here.
SYS1/SYS
SYS1/SYS is the command line interpreter overlay, loaded into the overlay region via RST 28H whenever the system needs to accept user input or process a DOS command. It provides six request codes (10H-70H) handling the "VTOS READY" prompt display, keyboard input acceptance, command parsing against a 36-entry library command table, filespec extraction in NAME/EXT.PASSWORD:DRIVE format, default extension insertion, and parenthesized parameter parsing with support for decimal numbers, hexadecimal values (X'nn' format), quoted strings, and YES/NO/ON/OFF booleans. Of the 36 library commands, 35 dispatch through the SYS6 overlay mechanism via RST 28H with SVC code 88H; only BOOT uses a direct address (4EA9H, a HALT instruction that freezes the CPU until hardware reset). The command table search at 50BCH is shared between the main command table and the parameter keyword table, using an efficient 8-byte-per-entry linear scan with short-name space-padding match support.
A disassembly of SYS1/SYS can be found here.
SYS2/SYS - File Open, Kill, and New File Creation
SYS2 is the file management overlay, handling three core operations: opening an existing file (request code 10H), killing/deleting a file (request code 20H), and creating a new file (request code 30H). It also provides shared utility routines for filespec parsing (request code 50H), password hash computation (request code 60H), and drive select with write-protect detection (request code 40H).
The Open Existing File handler performs a two-level directory search: an 8-bit CRC hash of the filename provides fast first-pass matching across directory sectors, followed by a full 11-byte filename comparison to confirm matches. Password verification uses a 16-bit polynomial CRC hash with a special case allowing blank passwords (hash 61A2H equivalent) to match any file. The directory search scans up to 8 sectors, advancing through directory entries in 32-byte increments within each 256-byte sector buffer.
The Kill File handler clears the system mode flag (bit 2 of 430FH) before delegating to the Open Existing File routine, then proceeds to deallocate the file's resources. The Open New or Existing File handler searches for a free directory slot using the system tick counter at 4040H as a pseudo-random starting offset to distribute new files across the directory, reducing clustering.
The FCB Population routine transfers directory metadata into the caller's FCB (File Control Block), filling fields from IX+00H through the extent chain data. Extent entries are copied from the directory into the FCB with FFH padding for unused slots. The drive select routine uses a 3-phase FDC index pulse polling technique to detect write-protect status, reading the WD1771 status register's bit 6 after synchronizing with the disk rotation.
A disassembly of SYS2/SYS can be found here.
SYS3/SYS
SYS3/SYS serves as the "cleanup" module for the VTOS 4.0 file management system. It ensures that all data is safely recorded, that disk space is correctly managed, and that file control blocks are properly finalized.
- CLOSE Processing (Request Code 10H): When a program finishes writing to a file, SYS3/SYS updates the directory entry with the final EOF offset and extent chain position. It also date-stamps the entry using the system date, walks the extent chain to count total allocated granules, computes how many the file actually requires, and deallocates any excess by clearing the appropriate bits in the Granule Allocation Table (GAT).
- Filespec Builder (Request Code 30H): Constructs a NAME/EXT:D filespec string from a directory entry and stores it directly into the FCB, terminated with a 03H load marker.
- FCB Initialization (Request Code 00H): Initializes a File Control Block for an unopened file reference, setting the FCB status to 2AH and recording the drive number, directory info byte, and default buffer pointer.
A disassembly of SYS3/SYS can be found here.
SYS4/SYS
SYS4/SYS is the error display overlay for VTOS 4.0, loaded into the overlay area at 4E00H-51DDH when an error condition triggers SVC code 86H via RST 28H. It is a pure display overlay containing no disk I/O logic except a single CALL to 4797H to read a directory sector for filename extraction.
SYS4 uses a dictionary-based message system to convert numeric error codes (00H-29H) into human-readable text. The system decomposes 42 error messages into 55 reusable words stored in a contiguous string pool, referenced through a 3-level lookup: an offset table maps each error code to a descriptor chain, each descriptor byte encodes a word index plus continuation and context flags, and a DEFW address table provides the start address and implied length of each word in the pool. The overlapping-entry scheme computes word length by subtraction of adjacent DEFW entries, eliminating stored lengths or delimiters. Total message system storage is approximately 613 bytes, roughly 20-25% smaller than individual full-text strings.
Error display includes three diagnostic components: an error code prefix ("*** ERRCOD=XX, "), a device/file context section with the associated drive and filename, and a return address display ("REFERENCED AT X'nnnn'") identifying the triggering instruction. The filename is extracted from either the FCB filespec string or from a directory entry via disk read. Fatal errors (bit 7 set) trigger a cold restart via JP M,0000H; recoverable errors exit to DOS READY via JP 4030H.
Compared to TRSDOS 2.3 SYS4, VTOS extends the error code range from 40 to 42 codes, adding "DEVICE IN USE" (27H) and "PROTECTED SYSTEM DEVICE" (28H), and expands the word dictionary from 53 to 55 entries.
A disassembly of SYS4/SYS can be found here.
SYS5/SYS
SYS5 is the DEBUG processor, including memory examination, modification, hex/ASCII display, and basic Z80 opcode decoding.
A disassembly of SYS5/SYS can be found here.
SYS6/SYS
SYS6/SYS, which contains most of the library commands, uses ISAM Directory Records and partitioned data to decide what to load. Roy Soltoff discusses this in THE LDOS QUARTERLY, Vol.1, No.4, on page 42.
Roy's Technical Corner
Roy's Technical Corner
This is the fourth in my regular series of articles on LDOS technical subjects. The first explained the Data Address Mark convention used in LDOS. The second detailed the functions of the @PARAM vector and how you can ease the development of your assembly language programming efforts by incorporating @PARAM to parse command line parameters. The third dealt with device independence and its implementation on the Models I and III. Commencing with this issue, my series has been named, " Roy's Technical Corner" - and you thought RTC meant Real Time Clock, ha!
One of the many interesting things unique to LDOS amongst the many TRS-80 DOSes is our extensive use of distinct record types in load modules. Load modules, you say? What's a load module? Let's set the record straight on this one and clear up some of the semantics concerning load modules (according to Pete Barbutti, semantics is the study of salmon and ticks}.
A load module is simply a file that contains information on where it is to load into memory. It is usually loaded by the SYSTEM loader. If it can be directly executed as a program, it then becomes known as an executable load module (ELM). The usual term that has been applied to such a file is "CMD". That's because a directly executable load module is thought of as a command. We further use the default file extension of /CMD for these command files (ever wonder where Command File Utility got its name?).
Another problem of semantics arises when we consider the output of the DUMP library command. The default file extension used is /CIM, short for core image. This has been an unfortunate specification because the core image dump is constructed exactly like a load module it, in fact, IS a load module file. The term core-image, assumed originally coined under TRSDOS by Randy Cook, does in fact mean executable load modules on main frame computers. It is generally NOT the case with the TRS-80, although it can be. I have problems with the use of the CIM extension, in light of the extensive use of CMD as a directly executable module. Perhaps we shall change the default in DUMP to something more meaningful.
Let's get back to the issue at hand. A load module has been said to include certain "loading" information pertinent to a loader routine. This is in contrast to a data file, a BASIC program, an ASSEMBLER source file, etc. which contain no such information. Think of the load module as a sequence of RECORDS. Note that I did not say an ordered sequence. Thus, the implication is that the records do not have to be in an ascending order (contiguous load addresses). Each record stands by itself and can be dealt with by a loader. The records must have some indicator as to what TYPE of record they are. This TYPE code is used to denote a record as a HEADER, a TRANSFER ADDRESS, an ISAM DIRECTORY, a LOAD RECORD, or other meaningful structure. A record must also have a LENGTH which is the length of the data area field. Under TRS-80 operating systems, the length is constrained to a one-byte value and can be from 1-256 in value. The remaining part of the record is its DATA AREA and is used to store program code, directory information, messages, etc. I have identified three different fields of information for the record; TYPE CODE, LENGTH BYTE, DATA AREA. If you are familiar with BASIC random access files, you will see the similarity in the fielding of records - except in this case, we have variable length sequentially accessed records (with partitioned data sets, we also have variable length indexed sequential accessed records).
Let me now build a table of record types used in LDOS. After that is accomplished, I will then fill in the details where necessary.
| Type | Data Area |
|---|---|
| 01 | Object code (load block) |
| 02 | Transfer address |
| 04 | End of partitioned data set member |
| 05 | Load module header |
| 06 | Partitioned data set header |
| 07 | Patch name header |
| 08 | ISAM directory entry |
| 0A | End of ISAM directory |
| 0C | PDS directory entry |
| 0E | End of PDS directory |
| 10 | Yanked load block |
| 1F | Copyright block |
Any code above X'1F' is invalid as a record type. In addition, any code not listed in the above table is reserved for future use. For example, codes 11-18 are reserved for relocatable code module structures as defined in the LC implementation. As an aside, Tandy uses a type code of X'03' in lieu of the transfer address type code of X'02' to indicate a load module that is not executable.
Let's look at a sample file. Start by listing the first sector of LBASIC via LIST LBASIC/CMD.BASIC (H). Notice it starts out with:
. . L B A S I C . . C ...
What you have here is a load module header (TYPE=05). The length byte (LENGTH=06) follows the TYPE code. The 6-byte DATA AREA field is the header name. ALL RECORDS FOLLOW THIS "FIELDING" ORDER. A record is organized with a TYPE, LENGTH, DATA sequence. The X'1F' begins the second record. Quick now, what kind is it? It happens to be a copyright record with a LENGTH of X'32' or 50 decimal bytes. Incidentally, the TYPE=1F record is generated automatically by the "COM" pseudo-op in EDAS, the assembler used to maintain LDOS.
Note that each record begins with the TYPE code and the first byte following the end of a record is always the TYPE code of the next record. The only exception is when a TYPE code indicates the end of a file. If you look further in the record displayed at relative position X'3C', or if you count 50 bytes down from the "C" of "Copyright", you will see:
The record TYPE is a load block (TYPE=01), and the length of the data area is X'A1', or 161 data bytes. The two-byte field following the LENGTH is the starting load address for the rest of the field. This is a special case. Since the LENGTH value includes the 2-byte load address, a length of x'03' would indicate only one load byte. A length of x'04' would indicate two load bytes. A length of X'FF' would indicate 253 load bytes. A length of X'00' would indicate 254 load bytes. To be able to have a data area with up to 256 bytes of loadable data, the LENGTH values of x'01' and X'02' are indicative of 255 and 256 load bytes respectfully. This is accomplished by having the system loader decrement the length value by two when reading a load address. The resultant value becomes the true length of the loadable data.
If you let the BASIC listing proceed to the end of the file, the last four bytes should appear as:
This will represent the TRANSFER ADDRESS record (TYPE=02). Again, we have a LENGTH byte which shows a 2-byte data field. The data field contains the transfer address or entry point to the program in standard low-order, high-order sequence. Of course, the transfer address value you will see is dependent on which LBASIC you are listing.
These are the four types you will find in other operating systems. Now it's time to get down to the nitty gritty. List the first record of SYS6/SYS via LIST SYS6/SYS.SYSTEM (H) {note: if you are not using 5.1.1, then the password to use is WOLVES}. Look at the area just past the copyright. You will see something like this:
The TYPE code of X'08' indicates an ISAM DIRECTORY RECORD. The LENGTH byte denotes a DATA area of six bytes. After the sixth byte, you will see another TYPE=08 starting another ISAM directory record. SYS6 is a partioned data set. The TYPE=08 records are its directory.
In LDOS, the directory data area is used by the SYSTEM loader to locate where a particular member can be found in the file. The data area has sub fields as follows. The first byte (1E) is the ISAM entry number. This value is provided to the SYSTEM loader by the SYS1 command line parsing routine upon the discovery of a library command request. The entry number is the PDS member that will execute your request. The SYSTEM loader will search the PDS directory for a match. The next two-byte sub-field is the transfer address of the member. The transfer address is contained in the directory so that more than one transfer address can be applied to a member (i.e. a member can have multiple entry points).
The three-byte field remaining is the triad pointer which points to the first byte of the member. The triad pointer is composed of the Next Record Number (NRN) and Relative Byte Offset for the member's first byte. Consult the LDOS Technical Reference Guide File Control Block section for more information on these values. Thus you have six bytes of data as specified by the LENGTH byte. In the PDS utility offered by MISOSYS, the ISAM directory record has a length of nine because it includes a three-byte subfield which contains the TRUE length of the member. That piece of information is needed in many PDS commands.
While you are looking at the first sector of 5Y56, proceed to the first byte following the last ISAM directory record. You will observe the sequence:
The TYPE=0A indicates that it is the end of a PDS directory. The SYSTEM loader uses this to discover that the requested ISAM entry happens to not be in the file being examined. If it gets to the TYPE=0A byte without a match on the ISAM number, the member is not in the directory. The LENGTH=01 is needed because ALL load module records MUST have a length byte. The DATA area contains only a single byte) X'00' We cannot indicate a null record because a length byte of X'00' indicates 256 data area bytes. Thus) the X'0A' record type must have a minimum of one byte in its data area.
The record following is a TYPE=04 to indicate the end of a PDS member. This record serves but one purpose when used immediately following the directory - it will result in a load file format error if you attempt to execute SYS6 or SYS7 as if they were CMD files. When not expecting a partitioned data set file) the SYSTEM loader will ignore record types other than X'01' and X'02' except for the X'04'. The file reading will terminate at the X'04' with the above-mentioned error message.
The record type X'04' is usually used at the end of a partitioned data set member. If you list through SYS6, you will discover that each member ends with "04 01 00". LDOS uses this code in lieu of the transfer address code because the SYSTEM loader needs to take action different from that when a standard load file has been completely loaded. Also, the transfer address for the member is stored in the ISAM directory itself.
The next record type to discuss is that used in a PDS MEMBER DIRECTORY. If you have purchased the PDS utility from MISOSYS, list it in hex. Notice that it starts with X'06' in lieu of an X'05' which is the normal header type for a load module. Well, PDS uses the X'06' in certain PDS commands to note whether the target file is a partitioned data set compatible with PDS utilities. There is a bit set in the LDOS system directory to indicate a file is a PDS; however that is to be used in a future release of PDS.
If you list past the front end loader, you will see the start of the PDS MEMBER DIRECTORY at relative 0001:14. It reads as follows:
. . d i r . . z . ...
The TYPE=0C indicates a PDS member directory record. The LENGTH byte specifies that the data area is an 11-byte field.
The DATA AREA is subfielded as an 8-byte member name (in lower case), a one-byte ISAM number that is used to match up with a corresponding ISAM directory record) and a 2-byte field of member data. The first byte uses bit position 7 to indicate a data member in contrast to an executable CMD program. Bit positions 4-6 are reserved for future use. Bits 0-3 and the next byte contain the date that the member was added to the PDS and is in a format identical to that explained as DIR+1 and DIR+2 in the DIRECTORY RECORD section of the LDOS Technical Reference manual. As you look through the PDS member directory, you will get to the "0E 01 00" record which indicates the end of the MEMBER directory. The front end loader uses this to note whether the requested member is in the PDS. The ISAM directory follows.
One last little one to wrap up is the record types associated with the PATCH utility. When you apply an X-patch to a file, the name of the patch file is used as a header name with a record type of X'07'. Thus, if you want to YANK the patch, the PATCH program can read through the file and search for a like-named header. If a matching header is found, PATCH will proceed as follows. Since it may be impossible to remove the patch without bubbling up any code blocks following the patch (another patch maybe?), PATCH will change the TYPE=01 records to TYPE=10 records. The TYPE=l0 records will not be loaded by the SYSTEM loader but will be considered as non loadable comment records. It is thus possible to "un-yank" a yanked patch; however, this feature is not implemented in the PATCH utility.
There we have it, the relatively complete explanation for load module format records.
A disassembly of SYS6/SYS can be found here.
SYS7/SYS
SYS7 is the extended utility / JCL / chaining command handler. It implements higher-level system commands (pause, delay, alert, keyin, chaining control, etc.) and produces the associated status messages.
A disassembly of SYS7/SYS can be found here.
SYS8/SYS
SYS8 handles directory entry creation, insertion, and management (adding new files to the directory, shifting entries, setting protection/attributes). It is the low-level support for commands that create or modify directory entries (like SAVE, OPEN for output, etc.).
A disassembly of SYS8/SYS can be found here.
SYS9/SYS
SYS9 overlay implements DEBUG commands that are invoked by single letters (B, E, F, L, Q, T, V, etc.). It is essentially a mini memory monitor / debugger command set.
A disassembly of SYS9/SYS can be found here.
SYS10/SYS
SYS10 is the low-level file-creation / directory-entry allocation overlay, performing the actual low-level work of allocating a new directory slot and preparing an FCB when a file is being created or opened for output. It has two main routines/purposes:
- Walk directory entries looking for 0FEH end markers, validates the target drive, clears or updates an 8-byte FCB structure, calls disk I/O helpers (4B10H, 4B1FH, 4F21H, etc.), and writes back drive parameters
- Scan the drive table starting at 4015H (stepping 8 bytes each time, falling back to 43C0H), checks protection and readiness bits, clears 8-byte blocks, and returns specific error codes (25H, 28H, 27H) if the operation cannot proceed.
A disassembly of SYS10/SYS can be found here.