So far, we’ve been working in DOS with NASM. That’s perfectly fine — DOS is small, simple, and great for beginners because it lets us directly see what’s happening inside the CPU without any modern protections.
But here’s the catch:
Assembly language is not the same everywhere.
Why? Because assembly is both machine-dependentandenvironment-dependent. The CPU instructions (mov, add, cmp, etc.) are the same, but the way you interact with the operating system (OS) changes.
That means the way you write and run code in:
DOS (real mode, 16-bit)
Linux (protected mode, 32/64-bit)
Windows (MASM/TASM style, 16/32/64-bit)
…will look different, even though the CPU underneath is the same.
In this section, we’ll explore how to structure assembly programs for different systems, and what changes when moving from one environment to another.
1. Structure of an Assembly Program
Every assembly program, regardless of system, has three basic parts:
Data Section → where we define variables, arrays, constants.
Code Section → where we write instructions (the logic).
Exit Mechanism → how we tell the OS “program finished.”
The big differences between DOS, Linux, and Windows are in the exit mechanism and how system services are called.
a) Assembly in DOS (NASM, Real Mode)
You’ve already seen this in our warmup codes. Here’s a quick recap:
[org 0x100] ; COM file starts at memory offset 0x100
; Code Section
mov ax, [num1] ; Load num1 into AX
mov bx, [num2] ; Load num2 into BX
add ax, bx ; Add them
mov [result], ax ; Store result
; Exit program
mov ax, 0x4C00
int 0x21
; Data Section
num1: dw 10
num2: dw 20
result: dw 0
[org 0x100] → tells assembler where the program will be loaded.
int 0x21 → DOS interrupt to terminate the program.
This is 16-bit real mode code.
b) Assembly in Linux (32-bit, NASM, ELF)
Linux doesn’t use DOS interrupts. Instead, it provides system calls with int 0x80 (in 32-bit).
section .data
num1 dd 10 ; define double word (32-bit)
num2 dd 20
result dd 0
section .text
global _start
_start:
mov eax, [num1] ; Load num1
mov ebx, [num2] ; Load num2
add eax, ebx ; Add
mov [result], eax ; Store result
; Exit system call
mov eax, 1 ; sys_exit
mov ebx, 0 ; exit code = 0
int 0x80
Key differences from DOS:
No [org 0x100]. Linux uses ELF executables.
Program entry is _start.
Exit is via system call (eax=1, int 0x80).
Segments are written as section .data and section .text.
c) Assembly in Linux (64-bit, NASM)
In 64-bit Linux, int 0x80 is deprecated. We use the syscall instruction with 64-bit registers.
.DATA
num1 DQ 10
num2 DQ 20
result DQ 0
.CODE
main PROC
mov rax, num1
add rax, num2
mov result, rax
mov ecx, 0 ; exit code
call ExitProcess
main ENDP
END
Key differences in Windows:
Requires directives (.MODEL, .DATA, .CODE).
Uses procedures (PROC / ENDP).
Exits with Win32 API call (ExitProcess), not interrupts.
What I’ve shown here are just small samples to highlight the style of assembly across different systems. The CPU instructions (mov, add, cmp) remain the same, but the syntax, structure, and exit mechanisms change.
Each environment (DOS, Linux, Windows) has its own conventions and rules. Whenever you encounter a new one, don’t be surprised if the code “looks different” — that’s just the system’s style. The key is: once you master the basics of assembly, adapting to these differences becomes much easier.