Every program DOS runs starts with a 256-byte program segment prefix (PSP), which DOS builds just before the program's code. A .COM's code starts at offset 100h because the PSP fills offsets 0 to FFh of the same segment. INT 21h AH=62h (DOS 3 and later) returns the PSP's segment in BX; the older 51h does the same.
In a .COM, CS, DS, ES and SS all hold the PSP segment at start, so 62h is not needed. In an .EXE, DS and ES point at the PSP only at start: once DS is loaded with @DATA, the PSP is reached by having saved ES first, or by asking DOS.
Layout
| Offset | Size | Contents |
|---|---|---|
| 00h | 2 bytes | CD 20: INT 20h, so a jump to PSP:0000 ends the program |
| 02h | word | the segment just past the program's memory block |
| 05h | 5 bytes | a far call into DOS, for CP/M-style programs |
| 0Ah | dword | INT 22h: where DOS goes when the program ends (the parent) |
| 0Eh | dword | INT 23h: the Ctrl+C handler to restore |
| 12h | dword | INT 24h: the critical error handler to restore |
| 16h | word | the parent's PSP segment |
| 18h | 20 bytes | the handle table: one byte per handle, FFh = unused |
| 2Ch | word | the environment's segment |
| 2Eh | dword | SS:SP saved by DOS on the last INT 21h |
| 32h | word | the number of handles |
| 34h | dword | the address of the handle table (normally PSP:18h) |
| 50h | 3 bytes | INT 21h, RETF: another way to call DOS |
| 5Ch | 16 bytes | FCB 1: the first argument, parsed as a file name |
| 6Ch | 20 bytes | FCB 2: the second argument |
| 80h | byte | the length of the command tail |
| 81h | 127 bytes | the command tail, ending with 0Dh; the default DTA |
MOV AH, 62h ; DOS: the PSP segment in BXINT 21hMOV ES, BX ; ES:0000 = the PSPMOV DX, OFFSET SAMEM1MOV AX, CSCMP AX, BX ; in a .COM they are the sameJE SAY1MOV DX, OFFSET OTHERM1SAY1:MOV AH, 09hINT 21hMOV DX, OFFSET FIRSTM1MOV AH, 09hINT 21hXOR SI, SIMOV AX, ES:[SI] ; the first two bytes: CDh, 20hXCHG AL, AH ; in reading orderCALL PHEXMOV DX, OFFSET INT20M1MOV AH, 09hINT 21hMOV DX, OFFSET TAILM1MOV AH, 09hINT 21hMOV AL, ES:[SI+80h] ; the length of the command tailMOV AH, 0CALL PDECMOV DX, OFFSET BYTESM1MOV AH, 09hINT 21hPUSH CSPOP ESSAMEM1 DB "PSP segment = CS", 13, 10, "$"OTHERM1 DB "PSP segment is not CS", 13, 10, "$"FIRSTM1 DB "PSP:0000 = $"INT20M1 DB " (INT 20h)", 13, 10, "$"TAILM1 DB "command tail: $"BYTESM1 DB " bytes", 13, 10, "$"PHEX PROC NEAR ; print AX as four hex digits (changes AX BX CX DX)MOV BX, AXMOV CX, 4PHEX1:PUSH CXMOV CL, 4ROL BX, CL ; the top digit to the bottomPOP CXMOV DL, BLAND DL, 0FhADD DL, "0"CMP DL, "9"JBE PHEX2ADD DL, 7 ; "A" to "F"PHEX2:MOV AH, 02hINT 21hLOOP PHEX1RETPHEX ENDPPDEC PROC NEAR ; print AX in decimal (changes AX BX CX DX)MOV BX, 10XOR CX, CX ; digits on the stackPDEC1:XOR DX, DXDIV BX ; AX = AX / 10, DX = the last digitPUSH DXINC CXOR AX, AXJNZ PDEC1PDEC2:POP DX ; the digits come back first to lastADD DL, "0"MOV AH, 02hINT 21hLOOP PDEC2RETPDEC ENDP
The command tail
PSP:80h holds the length and PSP:81h the text typed after the program's name, starting with the space that separated them and ending with a carriage return that the length does not count. The shell takes out redirections (>OUT.TXT, <IN.TXT, pipes) before the program sees the line, but leaves wildcards and capitals as typed. The default DTA lies over the same bytes (see INT 21h AH=1Ah), so read the tail before any find first.
The environment at PSP:2Ch is a list of NAME=value strings, each ending with a 0 byte, and an empty string. From DOS 3 on, a word (usually 1) and the program's own full path follow it: how a program finds the directory it was started from.