If you’re new to 8086 Assembly Language, running your first program on a modern 64-bit Windows system can feel confusing. But don’t worry — this step-by-step guide will help you install MASM and DOSBox, set up the environment, and write your first Hello World program in assembly. Along the way you will also learn why each step works, with diagrams, six tested practice programs, a debugging guide, and a troubleshooting table.
Install MASM and DOSBox, then assemble, link and run a program (Steps 1 to 5).
Registers, segments, flags, addressing modes and interrupts, explained with pictures.
Six working programs: text output, addition, multi-digit numbers, keyboard input, loops and decisions.
Read error messages, step through code with DEBUG, and fix the most common problems.
🗺️ The Big Picture: From .ASM to a Running Program
Before typing any commands, it helps to know what is going to happen. The CPU cannot read your assembly text directly. It only understands machine code (plain numbers). Two tools turn your text into something runnable, and DOS then loads the result into memory.
Every file you meet in this tutorial has a clear job:
| File | Made by | What it is |
|---|---|---|
hello.asm | You (EDIT, Notepad, VS Code) | Source code: assembly text you can read. |
hello.obj | MASM | Object file: your instructions turned into machine code, but addresses are not final yet. |
hello.lst | MASM (optional) | Listing: your source side by side with the machine code and addresses. Great for learning. |
hello.crf | MASM (optional) | Cross-reference data (used by the separate CREF utility). You can ignore it. |
hello.exe | LINK | The finished program that DOS can run. |
hello.map | LINK (optional) | A map showing where each segment sits inside the .EXE. |
🖥️ Step 1: Download MASM and DOSBox
You’ll need two tools:
- MASM (Microsoft Macro Assembler) – for writing and assembling your 8086 code.
- DOSBox – an emulator that lets you run old DOS applications on Windows 10/11.
After downloading:
- Extract MASM to your C: drive. This creates a folder:
C:\8086 - Install DOSBox normally.
Why do we need DOSBox at all? The MASM in this download (version 5.00) is a 16-bit DOS program. 64-bit editions of Windows 10 and 11 no longer include the old 16-bit subsystem, so they cannot run it directly. DOSBox pretends to be an old DOS PC, so MASM runs inside it exactly as it did in the 1980s.
Once you have completed the installation, open the DosBox application from your desktop screen. You will see two windows:

🔗 Step 2: Mount the 8086 Folder in DOSBox
- Open DOSBox from your desktop.
- Type the following commands:
mount c c:\8086
c: (Guide for you: type mount c c:\8086 and press Enter, followed by c: and press Enter again.)
This mounts the 8086 folder as drive C: inside DOSBox. DOSBox only sees the folders you hand to it, so mount is how you hand it the folder that holds MASM and your programs.
mount doesn't copy anything. It makes a Windows folder show up as a drive inside DOSBox, so a file you save in one place is instantly visible in the other.mount c c:\8086 says: “make the Windows folder C:\8086 appear as drive C:”. DOSBox answers with the Drive C is mounted message.c: switches from DOSBox’s built-in drive Z: to your new drive C:.C:\>. You are ready to run MASM, LINK and your own programs from here.💡 Pro tip: skip the typing next time. DOSBox can run commands automatically every time it starts. In the Windows Start menu open DOSBox-0.74 → Options → DOSBox 0.74 Options (it opens the config file in Notepad), scroll to the very bottom, and add these two lines under [autoexec]. If your folder is somewhere else, change the path.
mount c c:\8086
c: A few DOSBox keys are worth knowing before you start:
| Keys | What they do |
|---|---|
Alt + Enter | Switch between full screen and a window. |
Ctrl + F11 / Ctrl + F12 | Slow down / speed up the emulated CPU if everything feels too fast or too slow. |
Ctrl + F10 | Capture or release the mouse if it gets “stuck” inside the DOSBox window. |
Ctrl + F5 | Save a screenshot of the DOSBox screen (handy for assignments). |
Ctrl + F9 | Close DOSBox immediately. |
✍️ Step 3: Write Your First Assembly Program
Use DOSBox’s editor to create your first program: You are now ready to write your first program. Open the editor by typing EDIT program_name.ASM, and a new window will appear as shown below where you can write your program.
EDIT hello.asm 
Paste the following code:
.MODEL SMALL
.STACK 100H
.DATA
MSG DB 'HELLO!$'
.CODE
MAIN PROC
MOV AX,@DATA
MOV DS,AX
LEA DX,MSG
MOV AH,9
INT 21H
MOV AH,4CH
INT 21H
MAIN ENDP
END MAIN 💡 Can’t paste into DOSBox? Classic DOSBox 0.74 has no clipboard support, so Ctrl + V may do nothing. Either type the program by hand, or write it in Notepad or VS Code and save it as C:\8086\hello.asm. DOSBox sees the file instantly, because the folder is mounted. Make sure Notepad does not secretly save it as hello.asm.txt (choose “All files” as the type).
Save the file as hello.asm and exit the editor. In the editor press Alt + F to open the File menu, then choose Save, and again Alt + F then Exit.

Once you have saved the program, exit the editor, and you will be returned to the previous DOSBox window. You should see C:\>.
Your first program, line by line
Every one of these eleven lines has a job. You will reuse most of them in every program you ever write, so it is worth understanding them now.
| Line | What it does |
|---|---|
.MODEL SMALL | Chooses the memory model: one code segment and one data segment, each up to 64 KB. That is plenty for learning. |
.STACK 100H | Reserves 100h = 256 bytes for the stack, where CALL, PUSH and interrupts keep their temporary data. |
.DATA | Starts the data segment, where your variables live. |
MSG DB 'HELLO!$' | Creates a variable called MSG. DB means “define byte(s)”. The $ is not printed; it tells DOS where the text ends. |
.CODE | Starts the code segment, where your instructions live. |
MAIN PROC … MAIN ENDP | Marks the beginning and end of a procedure named MAIN. |
MOV AX,@DATAMOV DS,AX | Points the DS register at your data segment. Two steps are needed because the 8086 cannot put a number straight into a segment register. See Figure 3 below. |
LEA DX,MSG | “Load Effective Address”: puts the address (offset) of MSG into DX. |
MOV AH,9INT 21H | Asks DOS for service number 9, “print the text at DS:DX up to the $”. |
MOV AH,4CHINT 21H | Asks DOS for service 4Ch, “end this program and return to DOS”. |
END MAIN | Ends the file and says that execution starts at MAIN. |
Why do we set DS ourselves?
When DOS starts your program it creates a small 256-byte header called the PSP (Program Segment Prefix) and points both DS and ES at it, not at your data. If you skipped MOV AX,@DATA and MOV DS,AX, LEA DX,MSG would look for the text in the wrong place and print garbage.
.DATA begins by pointing DS at its own data. The exact order of the segments is decided by LINK.What the assembler really produces
Each mnemonic is just a human-friendly name for a few bytes. This is what MASM and LINK turn the program into. The whole program is only about 17 bytes of code:
| Assembly | Machine code (hex) | Meaning |
|---|---|---|
MOV AX,@DATA | B8 xx xx | B8 = “load AX with a 16-bit number”. The number (xx xx) is the data segment, filled in when DOS loads the program. |
MOV DS,AX | 8E D8 | Copy AX into DS. |
LEA DX,MSG | 8D 16 xx xx | Load DX with the offset of MSG (xx xx is that offset). |
MOV AH,9 | B4 09 | Load AH with 9. |
INT 21H | CD 21 | CD = “software interrupt”, 21 = which one. |
MOV AH,4CH | B4 4C | Load AH with 4Ch. |
INT 21H | CD 21 | Call DOS again to exit. |
And the text itself sits in the data segment as the bytes 48 45 4C 4C 4F 21 24, which are the ASCII codes of H E L L O ! $.
⚙️ Step 4: Assemble and Link the Program
Now you will write the following command to assemble your program:
masm program-name.asm In our example, I have used the following command:
masm hello.asm Then press Enter. MASM prints its banner and asks three questions, and you simply press Enter after each one to accept the defaults. This is what the whole exchange looks like:
.OBJ file MASM will create. Enter accepts HELLO.OBJ.NUL is DOS’s “black hole”, so Enter means “don’t create a listing”.👉 “If there are any errors or warnings, they will be displayed. To fix them, open your file again by typing EDIT program_name.ASM. Once corrected, reassemble the program.”
Now it is time to link your program. link program_name_without_extension. In our example, we use the following:
link hello Press Enter. You will see the following:
HELLO.EXE..MAP file. NUL means skip it.If LINK prints nothing else, the build worked and a new file HELLO.EXE now sits in C:\8086. Type dir to see it.
💡 Pro tip: Add a semicolon to skip all of MASM's prompts and accept the defaults. Type masm hello.asm; and then link hello; — each command finishes in one step.
Want to see the machine code? Ask MASM for a listing file with masm hello.asm,,hello.lst; and then display it with type hello.lst. You will see each source line next to the address and the bytes it became, exactly like the table in the previous step. Likewise, link hello,,hello.map; creates a map of your program’s segments.
▶️ Step 5: Run Your Program
Finally, run the program:
hello.exe (You can leave off .exe and simply type hello.)
✅ You should see:
HELLO! HELLO!. That is normal: the program never printed a new line. To fix it, store a carriage return (0DH) and a line feed (0AH) before the $: MSG DB 'HELLO!',0DH,0AH,'$'🎉 Congratulations — you’ve just run your first 8086 Assembly Program!
To exit DOSBox:
exit 🧠 Understanding What You Just Did: 8086 Basics
Your program worked, but why did it work? This module explains the ideas behind those eleven lines. You do not need to memorise it. Read it once, then come back whenever a later program uses something unfamiliar.
The 8086 in 30 seconds
The Intel 8086 (1978) is a 16-bit processor: its registers and data bus are 16 bits wide. Its address bus is 20 bits wide, so it can reach 220 bytes = 1 MB of memory. Modern PC processors still start up in a compatible “real mode”, which is why the same ideas apply in DOSBox. For the hardware side (pins, queue, BIU and EU) read Features of the Intel 8086 Microprocessor.
Registers: the CPU’s workbench
A register is a tiny, very fast storage slot inside the CPU. Almost every instruction works on registers, so learning their names and favourite jobs makes everything else easier.
In your Hello program you have already used four of them: AX (to carry the data-segment number), DS (to hold it), DX (the address of the text) and AH (the DOS service number).
Segments and the 20-bit address
A 16-bit register can only count to 65,535, yet the 8086 can address a whole megabyte. The trick is to use two registers for every address: a segment that says which 64 KB window, and an offset that says where inside the window. The CPU multiplies the segment by 16 (shifts it left by one hex digit) and adds the offset.
Because the shift is only 4 bits, segments overlap, and many different segment:offset pairs name the same byte. That is why you will sometimes see an address written like 1000:0123. When you write MOV AL,[BX], the CPU quietly pairs BX with DS; when you PUSH, it pairs SP with SS.
The FLAGS register: how the CPU remembers what just happened
After most arithmetic instructions, the CPU records facts about the result in single-bit flags: was it zero, was it negative, did it carry out? Conditional jumps read these flags to make decisions, so flags are what turns a list of instructions into a program that can choose.
JE and JG read them.| Flag | Name | Becomes 1 when… | DEBUG shows (0 / 1) |
|---|---|---|---|
CF | Carry | an unsigned add carries out of the top bit, or a subtract needs to borrow | NC / CY |
PF | Parity | the low byte of the result has an even number of 1 bits | PO / PE |
AF | Auxiliary carry | there is a carry out of bit 3 (used by BCD arithmetic) | NA / AC |
ZF | Zero | the result is zero (or a CMP found two equal values) | NZ / ZR |
SF | Sign | the top bit of the result is 1 (negative as a signed number) | PL / NG |
OF | Overflow | a signed result is too big or too small for the register | NV / OV |
DF | Direction | string instructions should walk memory downward (STD); 0 means upward (CLD) | UP / DN |
IF | Interrupt enable | the CPU accepts hardware interrupts (STI); CLI clears it | DI / EI |
TF | Trap | single-step mode, used by debuggers | (not shown) |
Try these two by hand (or watch them in DEBUG, see the debugging section):
| Code | Result in AL | CF | ZF | SF | OF | Why |
|---|---|---|---|---|---|---|
MOV AL,255ADD AL,1 | 00h | 1 | 1 | 0 | 0 | As an unsigned number 256 does not fit in a byte, so it carries and wraps to 0. As a signed number it is just −1 + 1 = 0, so there is no signed overflow. |
MOV AL,127ADD AL,1 | 80h | 0 | 0 | 1 | 1 | 127 + 1 fits as an unsigned number (128), but as a signed byte 80h means −128, so the signed result is wrong: overflow. |
Addressing modes: how instructions find their data
An addressing mode is simply a way of saying where an operand lives: in the instruction itself, in a register, or somewhere in memory. For memory, the 8086 adds up to three pieces to get the offset (called the effective address), then combines it with a segment register.
| Mode | Example | What happens |
|---|---|---|
| Register | MOV AX,BX | Copies BX into AX. Fastest, no memory involved. |
| Immediate | MOV AX,5 | The number 5 is stored inside the instruction. |
| Direct | MOV AL,MSG or MOV AL,[1234H] | Reads the memory byte at a fixed offset. |
| Register indirect | MOV AL,[BX] | Reads the byte whose offset is stored in BX (SI, DI and BP work too). |
| Based | MOV AL,[BX+4] | BX (or BP) plus a displacement. |
| Indexed | MOV AL,[SI+4] | SI (or DI) plus a displacement. Handy for walking along an array. |
| Based indexed | MOV AL,[BX+SI] | A base register plus an index register. |
| Based indexed with displacement | MOV AL,[BX+SI+4] | All three pieces together, for example a field inside a record in an array. |
Illegal combinations the assembler will reject: MOV AL,[AX] (only BX, BP, SI and DI may point at memory), MOV AL,[BX+BP] and MOV AL,[SI+DI] (two bases or two indexes), and MOV VAR1,VAR2 (the 8086 cannot copy memory to memory in one instruction; go through a register such as AL).
A tour of the instruction set
| Group | Common instructions | What they are for |
|---|---|---|
| Data transfer | MOV XCHG PUSH POP LEA IN OUT XLAT | Copy data between registers, memory, the stack and I/O ports. |
| Arithmetic | ADD ADC SUB SBB INC DEC NEG CMP MUL IMUL DIV IDIV | Add, subtract, compare, multiply and divide. The I versions are signed. |
| Logic | AND OR XOR NOT TEST | Work on individual bits: masking, setting, clearing, flipping. XOR AX,AX is the classic way to zero a register. |
| Shift and rotate | SHL SHR SAR ROL ROR RCL RCR | Slide bits left or right. Shifting left by 1 doubles a number; right by 1 halves it. |
| Jumps and calls | JMP Jcc LOOP CALL RET INT IRET | Change which instruction runs next: decisions, loops, procedures and interrupts. |
| String | MOVSB CMPSB SCASB LODSB STOSB REP | Copy, compare or scan blocks of bytes, with REP repeating them CX times. |
| Processor control | CLC STC CLI STI CLD STD NOP HLT | Set or clear flags, do nothing, or halt. |
Three 8086 limits that surprise beginners. (1) A shift or rotate by more than 1 needs the count in CL: SHL AL,4 is rejected, so write MOV CL,4 then SHL AL,CL. (2) PUSH 5 is not allowed; load the value into a register first and push that. (3) There are no 32-bit registers such as EAX; those arrived with the 80386.
Conditional jumps (Jcc) read the flags after a CMP. Choose the unsigned family for plain counts and characters, and the signed family when negative numbers are possible. Each conditional jump can only reach about 128 bytes backwards or 127 forwards.
After CMP a,b | Unsigned | Signed | Flags tested |
|---|---|---|---|
| a = b | JE / JZ | ZF = 1 | |
| a ≠ b | JNE / JNZ | ZF = 0 | |
| a > b | JA | JG | unsigned: CF = 0 and ZF = 0 signed: ZF = 0 and SF = OF |
| a ≥ b | JAE | JGE | unsigned: CF = 0 signed: SF = OF |
| a < b | JB | JL | unsigned: CF = 1 signed: SF ≠ OF |
| a ≤ b | JBE | JLE | unsigned: CF = 1 or ZF = 1 signed: ZF = 1 or SF ≠ OF |
Talking to DOS and the BIOS: interrupts
Your program does not draw letters on the screen itself. It asks the operating system to. An interrupt is a numbered doorway into DOS or the BIOS: you put a service number in AH, fill in any inputs, and execute INT n.
INT 21H. DOS does the work and may return a result in AL.INT 21H, the DOS services you will use most:
| AH | Service | You give | You get |
|---|---|---|---|
01h | Read one key (and show it on screen) | nothing | AL = the character |
02h | Print one character | DL = the character | nothing |
08h | Read one key without showing it | nothing | AL = the character |
09h | Print a string that ends with $ | DS:DX = address of the string | nothing |
0Ah | Read a whole line (until Enter) | DS:DX = a buffer; its first byte is the maximum length | the buffer’s second byte is the number of characters typed |
4Ch | End the program | AL = return code (0 = fine) | never returns |
BIOS services talk to the hardware more directly. The ones below are the key to text positioning and graphics (which answers the FAQ about graphics in DOSBox):
| Call | Service | Inputs |
|---|---|---|
INT 10H, AH=00h | Set the video mode | AL=03h for 80×25 text, AL=13h for 320×200 graphics with 256 colours |
INT 10H, AH=02h | Move the cursor | BH = page (0), DH = row, DL = column |
INT 10H, AH=0Eh | Print a character and advance the cursor | AL = character |
INT 10H, AH=0Ch | Plot one pixel (graphics modes) | AL = colour, CX = x, DX = y, BH = page |
INT 16H, AH=00h | Wait for a key press | returns AL = ASCII code, AH = scan code |
A small example that clears the screen and moves the cursor to row 5, column 10:
MOV AX,0003H ; video mode 3 = 80x25 text; setting a mode also clears the screen
INT 10H
MOV AH,2 ; set cursor position
MOV BH,0 ; page 0
MOV DH,5 ; row 5
MOV DL,10 ; column 10
INT 10H Numbers versus characters: ASCII
To the CPU a character is just a number: the letter A is 41h, and the digit character '7' is 37h. The actual value 7 is a different thing from the character '7'. That is why the “Add Two Numbers” program must add 30h before it prints: INT 21H prints characters, not values.
🧮 Practice Program 2: Add Two Numbers
Once Hello World works, try this program. It adds 3 and 4, converts the result to an ASCII character and prints it on the screen.
.MODEL SMALL
.STACK 100H
.DATA
MSG DB 'SUM = $'
.CODE
MAIN PROC
MOV AX,@DATA
MOV DS,AX
LEA DX,MSG ; print "SUM = "
MOV AH,9
INT 21H
MOV AL,3 ; first number
ADD AL,4 ; AL = 7
ADD AL,30H ; convert 7 to ASCII '7'
MOV DL,AL ; print the character in DL
MOV AH,2
INT 21H
MOV AH,4CH ; exit to DOS
INT 21H
MAIN ENDP
END MAIN Save it as add.asm, then run masm add.asm;, link add; and add. You should see SUM = 7.
The key line is ADD AL,30H. The CPU computed the number 7, but the screen needs the character '7', whose code is 37h (see the ASCII figure above). This version only works for single-digit results (0–9). The next program removes that limit.
🔢 Practice Program 3: Print Any Number (Multi-Digit Output)
To print a number such as 3825, we cannot just add 30h, because 3825 is not a single digit. The standard trick: divide by 10 again and again. Each remainder is the next digit, but they pop out last digit first. The stack reverses them for us.
.MODEL SMALL
.STACK 100H
.DATA
MSG DB 'SUM = $'
.CODE
MAIN PROC
MOV AX,@DATA
MOV DS,AX
LEA DX,MSG ; print "SUM = "
MOV AH,9
INT 21H
MOV AX,2468 ; first number
ADD AX,1357 ; AX = 3825
CALL PRINT_NUM ; print AX in decimal
MOV AH,4CH ; exit to DOS
INT 21H
MAIN ENDP
; PRINT_NUM - prints the unsigned number in AX (0 to 65535)
PRINT_NUM PROC
PUSH AX ; save the registers we are about to change
PUSH BX
PUSH CX
PUSH DX
MOV BX,10 ; divisor
XOR CX,CX ; CX counts the digits
NEXT_DIGIT:
XOR DX,DX ; clear DX: the dividend is DX:AX
DIV BX ; AX = quotient, DX = remainder (one digit, 0-9)
PUSH DX ; save the digit on the stack
INC CX
CMP AX,0 ; anything left to divide?
JNE NEXT_DIGIT
PRINT_LOOP:
POP DX ; digits come back in reverse order = correct order
ADD DL,30H ; digit -> ASCII
MOV AH,2
INT 21H
LOOP PRINT_LOOP ; CX = CX - 1, repeat while CX is not 0
POP DX ; restore the registers
POP CX
POP BX
POP AX
RET
PRINT_NUM ENDP
END MAIN How it works:
PRINT_NUMis a separate procedure, called withCALL. It starts byPUSHing the registers it will change and ends byPOPping them back, so the caller’s values are untouched.XOR DX,DXclears DX before everyDIV BX. A 16-bitDIVdivides the 32-bit numberDX:AX; if DX contains leftover junk, the answer will not fit and DOS stops the program with Divide overflow.- After
DIV BX(BX = 10) the quotient is in AX and the remainder, one decimal digit, is in DX. We push it and count it in CX. - When the quotient reaches 0 we stop dividing and
POPthe digits back one at a time, add 30h, and print them withINT 21H,AH=2.
You can paste PRINT_NUM into any later program: put the number in AX and write CALL PRINT_NUM. It prints any unsigned value from 0 to 65535 (tested with 0, 10, 3825 and 65535).
⌨️ Practice Program 4: Read a Key and Convert It to Uppercase
So far the programs talk to you. This one listens. It reads a key with INT 21H, AH=1, checks that it is a lowercase letter, and converts it by subtracting 20h (see the second half of the ASCII figure).
.MODEL SMALL
.STACK 100H
.DATA
PROMPT DB 'Type a lowercase letter: $'
RESULT DB 0DH,0AH,'Uppercase is : $'
BADMSG DB 0DH,0AH,'That is not a lowercase letter.',0DH,0AH,'$'
NEWLINE DB 0DH,0AH,'$'
.CODE
MAIN PROC
MOV AX,@DATA
MOV DS,AX
LEA DX,PROMPT
MOV AH,9
INT 21H
MOV AH,1 ; read one character (echoed), result in AL
INT 21H
CMP AL,'a'
JB NOT_LOWER ; below 'a' -> not a lowercase letter
CMP AL,'z'
JA NOT_LOWER ; above 'z' -> not a lowercase letter
SUB AL,20H ; 'a' is 61H, 'A' is 41H: subtract 20H
MOV BL,AL ; keep the answer: INT 21H / AH=9 overwrites AL
LEA DX,RESULT
MOV AH,9
INT 21H
MOV DL,BL ; print the converted letter
MOV AH,2
INT 21H
LEA DX,NEWLINE ; end the line so the DOS prompt starts on a new one
MOV AH,9
INT 21H
JMP DONE
NOT_LOWER:
LEA DX,BADMSG
MOV AH,9
INT 21H
DONE:
MOV AH,4CH
INT 21H
MAIN ENDP
END MAIN What to notice:
CMP AL,'a'followed byJB NOT_LOWERmeans “jump if AL is below a”. We use the unsigned jumps (JB,JA) because character codes are plain unsigned numbers.- The answer is kept in
BLbefore printing, becauseINT 21HwithAH=9overwrites AL (it returns the$character there). NEWLINE DB 0DH,0AH,'$'is a reusable string that moves the cursor to the start of the next line.
🔁 Making Programs Think: Loops and Decisions
Programs 5 and 6 are about the two things that make a computer useful: repeating work and choosing between alternatives. Both rely on jumps, and Figure 11 shows how each flow works.
LOOP counts down in CX and jumps back until CX reaches 0. CMP compares two values and a conditional jump decides which path to take.Practice Program 5: Count with a Loop
Put the number of repetitions in CX, write the body, and finish it with LOOP label. Each time LOOP runs it subtracts 1 from CX and jumps back unless CX has become 0.
.MODEL SMALL
.STACK 100H
.CODE
MAIN PROC
MOV CX,10 ; repeat 10 times
MOV BL,'0' ; first character to print
AGAIN:
MOV DL,BL
MOV AH,2
INT 21H ; print the digit
MOV DL,' '
MOV AH,2
INT 21H ; print a space
INC BL ; next digit
LOOP AGAIN ; CX = CX - 1, jump back while CX is not 0
MOV AH,4CH
INT 21H
MAIN ENDP
END MAIN 🏆 Challenge: print a triangle of stars (nested loops)
Here the inner loop needs its own counter, so it uses CX while the outer loop uses BX as the row number. The two MOV DL lines before INT 21H print a carriage return and a line feed to finish each row.
.MODEL SMALL
.STACK 100H
.CODE
MAIN PROC
MOV BX,1 ; BX = stars on the current row (1..5)
ROW:
MOV CX,BX ; inner loop: print BX stars
STARS:
MOV DL,'*'
MOV AH,2
INT 21H
LOOP STARS
MOV DL,0DH ; carriage return
MOV AH,2
INT 21H
MOV DL,0AH ; line feed
MOV AH,2
INT 21H
INC BX
CMP BX,5
JBE ROW ; repeat while BX <= 5
MOV AH,4CH
INT 21H
MAIN ENDP
END MAIN Practice Program 6: Find the Larger of Two Numbers
Variables live in the data segment. CMP AL,BL subtracts BL from AL without storing the answer; it only updates the flags. Then JGE SHOW (“jump if greater or equal”) decides which number to print. Change the two values at the top and run it again.
.MODEL SMALL
.STACK 100H
.DATA
FIRST DB 7
SECOND DB 4
MSG DB 'Larger number = $'
.CODE
MAIN PROC
MOV AX,@DATA
MOV DS,AX
LEA DX,MSG
MOV AH,9
INT 21H
MOV AL,FIRST ; AL = first number
MOV BL,SECOND ; BL = second number
CMP AL,BL ; AL - BL: sets the flags, result is thrown away
JGE SHOW ; AL >= BL (signed): AL already holds the larger one
MOV AL,BL ; otherwise BL is larger
SHOW:
ADD AL,30H ; digit -> ASCII
MOV DL,AL
MOV AH,2
INT 21H
MOV AH,4CH
INT 21H
MAIN ENDP
END MAIN Signed or unsigned? JGE, JG, JL and JLE treat the numbers as signed (the byte 80h means −128). JAE, JA, JB and JBE treat them as unsigned (80h means 128). For small digits like 7 and 4 both work; for characters and large counts, use the unsigned family. The full table is in the instruction-set section above.
Now try these yourself
- Print your name on two separate lines (use
0DH,0AHbetween them). - Print the digits from 9 down to 0 (start BL at
'9'and useDEC BL). - Read two single-digit numbers with
AH=1, subtract 30h from each, add them, and print the sum withPRINT_NUM. - Read a digit and print EVEN or ODD. Hint:
TEST AL,1sets ZF when bit 0 is 0. - Print the whole alphabet
AtoZwith a loop (26 repetitions). - Store the text
'ASSEMBLY$'and count how many times the letterSappears, using SI as a pointer and a loop.
Need ideas or finished solutions? See Assembly Language Programming Codes for the 8086 Microprocessor.
Starter template. Copy this skeleton for every new program. It assembles and runs cleanly (it just does nothing), and every real program in this tutorial is built on top of it.
.MODEL SMALL
.STACK 100H
.DATA
; your variables go here, for example: MSG DB 'Hi$'
.CODE
MAIN PROC
MOV AX,@DATA ; set up DS (skip these two lines if you have no .DATA)
MOV DS,AX
; ---- your code goes here ----
MOV AH,4CH ; exit to DOS
INT 21H
MAIN ENDP
END MAIN 🔍 Debugging: Watching Your Program Think
When a program prints the wrong thing, guessing is slow. A debugger lets you run one instruction at a time and look at the registers and memory after each step. The classic DOS tool is DEBUG.
Heads-up: DOSBox does not include DEBUG.EXE. If it is not already in your C:\8086 folder, copy it there (for example the free DEBUG that comes with FreeDOS), or use CodeView (CV.EXE) if your MASM package includes it, or the built-in debugger of an emulator such as emu8086.
Start it with your program as the argument:
debug hello.exe The prompt changes to a dash (-). These are the commands you need:
| Command | What it does | Example |
|---|---|---|
R | Show all registers, the flags, and the next instruction. | R or R AX |
T | Trace: run one instruction (or n of them). | T, T 3 |
P | Proceed: like T, but runs a whole INT or CALL in one go. | P |
G | Go: run until the program ends. | G |
U | Unassemble: show the machine code as instructions. | U |
D | Dump memory as hex and text. | D DS:0 |
E | Enter new byte values into memory. | E DS:0 |
Q | Quit DEBUG. | Q |
A five-minute experiment with the Hello program:
- Type
R. Look at the line withDS=andES=: they hold the same number, the PSP. IP is 0000 and the first instruction,MOV AX,…, is shown below. - Type
Ttwice to runMOV AX,@DATAandMOV DS,AX, thenRagain. DS now equals the data-segment number that was in AX. This is the moment shown in the memory-layout figure above. - Type
D DS:0. The first bytes are48 45 4C 4C 4F 21 24, withHELLO!$in the text column on the right. - Type
TforLEA DX,MSG, thenR: DX should be 0000, the offset of MSG. TypeTforMOV AH,9and check AH. - Type
Pto runINT 21Hin one step.HELLO!appears on the screen. Finish withG; DEBUG reports that the program terminated normally. UseQto leave.
The flag letters at the end of the R output (NV UP EI PL NZ NA PO NC) are the nine flags from the FLAGS table. Trace through the MOV AL,255 / ADD AL,1 example and watch ZR and CY appear.
🧯 Common Errors and How to Fix Them
Almost everyone hits these. Match what you see on the left, then apply the fix on the right.
| What you see | Likely cause | Fix |
|---|---|---|
Bad command or file name | The program is not in the mounted folder, you are still on drive Z:, or there is a typo. | Type c:, then dir and check that MASM.EXE, LINK.EXE and your .asm file are listed. Run the mount command again if they are not. |
| MASM reports Severe Errors with a line number | A typo: a misspelled instruction or label, a label that was never defined, or a missing END. | Open the file (EDIT hello.asm), go to the reported line, and read MASM’s message (for example Symbol not defined). Fix the first error first; later ones are often knock-on effects. |
LINK : warning L4021: no stack segment | The program has no .STACK line. | Add .STACK 100H right after .MODEL SMALL, then assemble and link again. Programs that use CALL, PUSH or INT need a stack. |
LINK : warning L4038: program has no starting address | The last line is just END with no label. | End with END MAIN, using the name of your procedure. |
| Strange characters print, or nothing prints | DS was never set, or the string has no $ at the end. | Check that MOV AX,@DATA and MOV DS,AX come first, and that every text ends with $. Without the $, DOS keeps printing whatever bytes follow in memory. |
| The program hangs, crashes, or the screen fills with junk after the output | No exit code. The CPU runs on into whatever bytes follow your program. | Finish with MOV AH,4CH and INT 21H. |
Divide overflow | A DIV got a result too big for the register, or a divisor of 0. Often DX was not cleared. | Put XOR DX,DX before DIV BX, and make sure the divisor is not 0. |
MASM rejects .MODEL TINY | The tiny model was added in MASM 6.0. Your MASM 5.00 does not know it. | Use .MODEL SMALL. |
| Jump “out of range” error | A conditional jump (JE, JL, …) can only reach about −128 to +127 bytes. | Invert the condition and jump over a long JMP: JNE SKIP, JMP FAR_LABEL, SKIP: |
Your file is really called hello.asm.txt | Notepad added a hidden .txt. | In Notepad choose Save as type: All files, or turn on “File name extensions” in Windows Explorer. |
| The DOS prompt appears right after your output | Your text has no line break. | Add 0DH,0AH before the $, or print them with AH=2. |
| DOSBox is too fast, too slow or jerky | The emulated CPU speed (cycles) does not suit your PC. | Press Ctrl + F11 to slow down or Ctrl + F12 to speed up. |
📋 Quick Reference
Bookmark this section. It is the short list of everything you type in this tutorial.
| Task | Command |
|---|---|
| Mount your folder and switch to it | mount c c:\8086 then c: |
| Create or edit a program | EDIT name.asm |
| Assemble (quick) | masm name.asm; |
| Assemble with a listing | masm name.asm,,name.lst; |
| Link (quick) | link name; |
| Run | name (or name.exe) |
| Debug | debug name.exe |
| List files / change folder / clear screen | dir / cd foldername / cls |
| Show a text file on screen | type name.lst |
| Copy, rename, delete a file | copy a.asm b.asm / ren a.asm b.asm / del a.obj |
| Quit DOSBox | exit |
❓ Frequently Asked Questions (FAQs)
Q1: Why do we need to assemble and link the program before running it?
A: Assembly code (.ASM) is written in human-readable mnemonics. The assembler (MASM) converts it into object code (.OBJ). The linker (LINK) then combines this object code with necessary system libraries to produce an executable (.EXE). Without assembling and linking, the computer cannot run your program.
Q2: Why do we use DOSBox instead of directly running MASM on Windows 10/11?
A: MASM 8086 is 16-bit software, which isn’t supported on 64-bit Windows. DOSBox emulates DOS so MASM can run.
Q3: Can I use TASM or emu8086 instead of MASM?
A: Yes. emu8086 is beginner-friendly with a built-in debugger. MASM + DOSBox is closer to real hardware usage.
Q4: Where should I place the MASM folder?
A: Place it in C:\8086. This makes mounting easier (mount c c:\8086). Any other folder works too, as long as the path in the mount command matches.
Q5: How do I fix “Bad command or file name” error in DOSBox?
A: Ensure MASM.EXE is inside the mounted folder and you typed the mount command correctly. Then type c: to switch to the drive and dir to confirm the file is listed. See the troubleshooting table for more.
Q6: Can I use Notepad instead of EDIT in DOSBox?
A: Yes. Write in Notepad/VS Code, then save as .ASM in C:\8086. Use DOSBox only to assemble and run.
Q7: Why doesn’t my message print correctly?
A: For INT 21h, AH=9, strings must end with $. Example: MSG DB 'HELLO!$'. Also check that you set DS with MOV AX,@DATA and MOV DS,AX.
Q8: What’s the difference between .MODEL SMALL and .MODEL TINY?
A: .MODEL SMALL → code and data in separate 64 KB segments (this produces an .EXE). .MODEL TINY → everything in one 64 KB segment, which produces a .COM file. Note that TINY only exists in MASM 6.0 and later; the MASM 5.00 used in this tutorial does not support it, so use SMALL.
Q9: Can I run graphics programs in DOSBox?
A: Yes. Use INT 10h BIOS interrupts for graphics. DOSBox supports both text and graphics modes. For example MOV AX,0013H followed by INT 10H switches to 320×200 graphics with 256 colours. See the BIOS table in the interrupts section.
Q10: How can I debug my assembly program?
A: Use DOS’s debug tool (debug hello.exe) or switch to emu8086, which has a built-in debugger. DOSBox does not ship DEBUG.EXE, so copy it into C:\8086 first. A step-by-step walkthrough is in the debugging section.
Q11: What is the difference between a .EXE and a .COM program?
A: A .COM file is a raw program that lives entirely in one 64 KB segment and starts at offset 100h. An .EXE file has a header, can use separate code, data and stack segments, and can be larger. The .MODEL SMALL programs in this tutorial are .EXE files.
Q12: What is the difference between MOV DX,OFFSET MSG and LEA DX,MSG?
A: For a simple variable they give the same result: DX receives the offset of MSG. LEA is more flexible because it can also calculate addresses like LEA DX,[BX+SI+4], which OFFSET cannot.
Q13: What do DB, DW and DD mean?
A: They define data: DB = define byte (8 bits), DW = define word (16 bits), DD = define double word (32 bits). For example COUNT DW 1000 reserves two bytes holding 1000, and NAME1 DB 'ABC' stores three characters.
Q14: Can I use 32-bit registers like EAX?
A: Not on the 8086, and not with MASM 5.00. Registers such as EAX were added with the 80386. This tutorial sticks to the 16-bit registers (AX, BX, CX, DX and friends), which is what the 8086 really has.
Q15: Does MASM care about upper and lower case?
A: By default no. mov ax,bx, MOV AX,BX and Mov Ax,Bx all work, and labels are matched without regard to case. Text inside quotes is stored exactly as you type it.
📚 Keep Learning 8086
- Assembly Language Programming Codes for the 8086 Microprocessor
- Features of the Intel 8086 Microprocessor: A Beginner's Guide
- Programmed I/O vs Interrupt-Driven I/O: Understanding the Difference
- Importance of I/O Module
Some useful program codes for practice: Assembly Language 8086 code examples
Did this tutorial help you? Comment below if you got stuck — I’ll reply with solutions!
it is helpful
ReplyDeleteSome people think that writing on computers is easy. But I know how difficult it is to come up with ideas and turn them into meaningful and interesting content. Private tutor near BethesdaNever give up, bro!
ReplyDeleteThank you. It is helps me
ReplyDelete