Writing Assembly Across Systems

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-dependent and environment-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:

  1. Data Section → where we define variables, arrays, constants.

  2. Code Section → where we write instructions (the logic).

  3. 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.

section .data
    num1   dq 10         ; define quad-word (64-bit)
    num2   dq 20
    result dq 0

section .text
    global _start

_start:
    mov rax, [num1]      ; Load num1
    mov rbx, [num2]      ; Load num2
    add rax, rbx         ; Add
    mov [result], rax    ; Store result

    ; Exit system call
    mov rax, 60          ; sys_exit = 60
    mov rdi, 0           ; exit code = 0
    syscall

Notice:

  • Registers are now 64-bit (rax, rbx, etc.).

  • Exit system call is rax=60.

  • Parameters are passed in registers (rdi for exit code).

d) Assembly in Windows (MASM/TASM style)

Windows doesn’t let us call kernel interrupts directly like DOS or Linux. Instead, we use the Win32 API.

Here’s a 32-bit MASM example:

.386
.MODEL FLAT, stdcall
option casemap:none

.DATA
    num1 DD 10
    num2 DD 20
    result DD 0

.CODE
start:
    mov eax, num1
    add eax, num2
    mov result, eax

    push 0                  ; exit code
    call ExitProcess
END start

And a 64-bit MASM example:

.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.

Last updated

Was this helpful?