DEBUG PROG.EXE loads the program as DOS would, stops before its first instruction and shows its prompt, a hyphen. Each command is a letter and its arguments, numbers are hexadecimal, and an address is segment:offset or an offset in the obvious segment (CS for code, DS for data). DOS up to 6.22 came with Microsoft's DEBUG; DOS-debug, in the catalog, is a free one with the same commands and more (it knows the 386's instructions and registers).

CommandDoes
U [addr] [L n]unassemble: disassemble from addr, or from CS:IP and on
R [reg]show the registers and the next instruction; R AX changes AX
T [=addr] [n]trace: run one instruction (or n), into calls and interrupts
P [=addr] [n]proceed: like T, but a CALL, INT, LOOP or REP runs to its end
G [=addr] [bp ...]go: run to the end, or to one of the breakpoints
D [addr] [L n]dump memory as hex and characters
E addr [bytes]enter bytes into memory
A [addr]assemble instructions typed in, from addr
H a bhex arithmetic: a + b and a - b
Qquit
?list the commands

A session

The two-module program from the article on modules, in DOS-debug (Microsoft's DEBUG prints the same, its flags line a little wider). U disassembles from the entry point; T runs one instruction; P runs the whole CALL PRINT, and the string appears in the middle of the session.

Run on a dosbench box; the segment numbers depend on where DOS loaded the program.
C:\TCHAIN>DEBUG PROG.EXE
-u
0E04:0000 B8050E MOV AX,0E05
0E04:0003 8ED8 MOV DS,AX
0E04:0005 BA0600 MOV DX,0006
0E04:0008 E80500 CALL 0010
0E04:000B B8004C MOV AX,4C00
0E04:000E CD21 INT 21
0E04:0010 B409 MOV AH,09
0E04:0012 CD21 INT 21
0E04:0014 C3 RET
0E04:0015 005477 ADD [SI+77],DL
-r
AX=0000 BX=FFFF CX=FE62 DX=0000 SP=0100 BP=0000 SI=0000 DI=0000
DS=0DF4 ES=0DF4 SS=0E08 CS=0E04 IP=0000 NV UP EI PL ZR NA PE NC
0E04:0000 B8050E MOV AX,0E05
-t
AX=0E05 BX=FFFF CX=FE62 DX=0000 SP=0100 BP=0000 SI=0000 DI=0000
DS=0DF4 ES=0DF4 SS=0E08 CS=0E04 IP=0003 NV UP EI PL ZR NA PE NC
0E04:0003 8ED8 MOV DS,AX
-t
AX=0E05 BX=FFFF CX=FE62 DX=0000 SP=0100 BP=0000 SI=0000 DI=0000
DS=0E05 ES=0DF4 SS=0E08 CS=0E04 IP=0005 NV UP EI PL ZR NA PE NC
0E04:0005 BA0600 MOV DX,0006
-p
AX=0E05 BX=FFFF CX=FE62 DX=0006 SP=0100 BP=0000 SI=0000 DI=0000
DS=0E05 ES=0DF4 SS=0E08 CS=0E04 IP=0008 NV UP EI PL ZR NA PE NC
0E04:0008 E80500 CALL 0010
-p
Two modules, one program
AX=0924 BX=FFFF CX=FE62 DX=0006 SP=0100 BP=0000 SI=0000 DI=0000
DS=0E05 ES=0DF4 SS=0E08 CS=0E04 IP=000B NV UP EI PL ZR NA PE NC
0E04:000B B8004C MOV AX,4C00
-d ds:0 l 20
0E05:0000 B4 09 CD 21 C3 00 54 77-6F 20 6D 6F 64 75 6C 65 ...!..Two module
0E05:0010 73 2C 20 6F 6E 65 20 70-72 6F 67 72 61 6D 0D 0A s, one program..
-g
Program terminated normally (0000)
-q

Look at what it shows. DS starts at the PSP (0DF4), as for every .EXE, until the program loads its own data segment. The data segment begins in the paragraph after the code (0E05:0000 is 0E04:0010), which is why the dump starts with PRINT's bytes and the string sits at offset 6. After the RET at 0014, U carries on disassembling the string as if it were code: DEBUG cannot tell code from data.

The flags

FlagSetClear
OF overflowOVNV
DF directionDNUP
IF interruptsEIDI
SF signNGPL
ZF zeroZRNZ
AF auxiliary carryACNA
PF parityPEPO
CF carryCYNC

A session from a file

DEBUG reads its commands from standard input, so a session can be written down once and replayed, and its output kept: DEBUG PROG.EXE < DBG.TXT > DBG.LOG. The file holds the commands one per line (here u, r, t, t, p, p, d ds:0 l 20, g, q) and must end with q, or DEBUG waits for more.

Pitfalls

T on an INT 21h walks into DOS itself, hundreds of instructions: use P. G without breakpoints runs to the end, and after "Program terminated normally" the program must be loaded again (DEBUG anew, or N PROG.EXE and L) before it can be stepped. A breakpoint is an INT 3 byte written over the code, so it must be on the first byte of an instruction.