DEV Community

Cover image for # Flat Assembler (FASM): A Practical Introduction to Low-Level Programming
Farhad Rahimi Klie
Farhad Rahimi Klie

Posted on

# Flat Assembler (FASM): A Practical Introduction to Low-Level Programming

If you are interested in operating systems, compilers, reverse engineering, embedded systems, executable formats, or simply understanding how computers work at a low level, learning assembly language can be extremely valuable.

There are many assemblers available today, but one that deserves special attention is Flat Assembler, commonly called FASM.

FASM is a fast, powerful, and flexible assembler that gives you a very direct relationship with the machine code generated by your program.

In this article, we'll look at what FASM is, how it works, why it is interesting, and what you can build with it.


What Is Flat Assembler?

Flat Assembler (FASM) is an assembler for the x86 and x86-64 architectures.

An assembler converts assembly language instructions into machine code that the CPU can execute.

For example:

mov rax, 60
Enter fullscreen mode Exit fullscreen mode

The CPU does not actually understand the text:

mov rax, 60
Enter fullscreen mode Exit fullscreen mode

Instead, FASM translates the instruction into machine-code bytes.

Conceptually:

Assembly source
      |
      v
     FASM
      |
      v
 Machine code
      |
      v
 Executable
      |
      v
     CPU
Enter fullscreen mode Exit fullscreen mode

This is the fundamental job of an assembler.


Why Is FASM Interesting?

FASM has several characteristics that make it different from many other assemblers.

1. It is self-hosting

FASM is written in assembly language.

That alone makes it an interesting project for people who want to study low-level programming.

2. It has a powerful macro system

FASM's macro system allows you to build your own higher-level abstractions while still working with assembly.

For example:

macro exit {
    mov rax, 60
    xor rdi, rdi
    syscall
}
Enter fullscreen mode Exit fullscreen mode

Then:

exit
Enter fullscreen mode Exit fullscreen mode

FASM expands the macro during assembly.

3. It can generate different executable formats

FASM supports multiple output formats.

For example, you can create:

format ELF64 executable
Enter fullscreen mode Exit fullscreen mode

for a Linux x86-64 executable.

You can also work with Windows PE formats and other output types.

4. It gives you direct control

FASM does not try to hide the machine.

You can explicitly work with:

  • registers
  • memory
  • instructions
  • sections
  • symbols
  • addresses
  • stack frames
  • calling conventions
  • system calls
  • executable formats

This makes it excellent for learning how software actually works.


FASM vs NASM

If you have already learned or seen NASM, you may wonder:

Why learn FASM instead?

Both are excellent assemblers, but their philosophies differ.

A simplified comparison:

Feature FASM NASM
x86/x86-64 Yes Yes
Linux Yes Yes
Windows Yes Yes
Powerful macros Yes Yes
Multiple output formats Yes Yes
Direct machine-level control Yes Yes
Self-hosting implementation Yes No
Minimal syntax Yes Yes

The biggest reason to explore FASM is its macro system and overall design philosophy.

FASM tries to provide powerful facilities while keeping the assembler itself relatively small and flexible.


Your First FASM Program

Let's start with a simple Linux x86-64 program.

format ELF64 executable

segment readable executable

entry main

main:
    mov rax, 60
    xor rdi, rdi
    syscall
Enter fullscreen mode Exit fullscreen mode

This program simply exits.

Let's understand it.


format

format ELF64 executable
Enter fullscreen mode Exit fullscreen mode

This tells FASM what kind of output we want.

Here:

ELF64
Enter fullscreen mode Exit fullscreen mode

means a 64-bit Linux ELF executable.

And:

executable
Enter fullscreen mode Exit fullscreen mode

means we want an executable file.

So FASM knows how to construct the final executable.


segment

segment readable executable
Enter fullscreen mode Exit fullscreen mode

This creates a segment containing executable code.

The permissions indicate that the segment can be:

readable
executable
Enter fullscreen mode Exit fullscreen mode

The CPU will execute instructions from this region.


entry

entry main
Enter fullscreen mode Exit fullscreen mode

This tells FASM that main is the entry point of the executable.

In other words, when the operating system starts the program, execution begins at:

main:
Enter fullscreen mode Exit fullscreen mode

Labels

This:

main:
Enter fullscreen mode Exit fullscreen mode

is a label.

A label gives a name to a location in the program.

For example:

main:
    mov rax, 60
Enter fullscreen mode Exit fullscreen mode

The label main represents the address of that instruction.

You can create other labels:

loop_start:
    ...
Enter fullscreen mode Exit fullscreen mode

or:

my_function:
    ...
Enter fullscreen mode Exit fullscreen mode

Labels are extremely important in assembly programming.


Registers

When programming x86-64 assembly, you will work heavily with registers.

Some important registers include:

RAX
RBX
RCX
RDX

RSI
RDI
RBP
RSP

R8
R9
R10
R11
R12
R13
R14
R15
Enter fullscreen mode Exit fullscreen mode

Each register is a small, extremely fast storage location inside the CPU.

For example:

mov rax, 123
Enter fullscreen mode Exit fullscreen mode

means:

RAX = 123
Enter fullscreen mode Exit fullscreen mode

You can then use the value:

add rax, 10
Enter fullscreen mode Exit fullscreen mode

Now:

RAX = 133
Enter fullscreen mode Exit fullscreen mode

The mov Instruction

One of the most important instructions is:

mov
Enter fullscreen mode Exit fullscreen mode

It copies data.

For example:

mov rax, 10
Enter fullscreen mode Exit fullscreen mode

means:

RAX <- 10
Enter fullscreen mode Exit fullscreen mode

Another example:

mov rbx, rax
Enter fullscreen mode Exit fullscreen mode

means:

RBX <- RAX
Enter fullscreen mode Exit fullscreen mode

It is important to understand that mov is fundamentally a copy operation.


Memory

Registers are very small.

When you need larger amounts of data, you work with memory.

FASM allows memory operands using brackets:

mov rax, [address]
Enter fullscreen mode Exit fullscreen mode

The brackets mean:

Access the memory located at this address.

For example:

mov rax, [rbx]
Enter fullscreen mode Exit fullscreen mode

means:

Use the value in RBX as an address
        |
        v
Memory[RBX]
        |
        v
       RAX
Enter fullscreen mode Exit fullscreen mode

Compare:

mov rax, rbx
Enter fullscreen mode Exit fullscreen mode

with:

mov rax, [rbx]
Enter fullscreen mode Exit fullscreen mode

The first copies the address/value stored in RBX.

The second reads the memory at the address stored in RBX.

This distinction is fundamental in assembly.


Data Definitions

FASM provides directives for creating data.

Some important ones are:

db
dw
dd
dq
Enter fullscreen mode Exit fullscreen mode

They represent different data sizes.

db

Define byte:

db 10
Enter fullscreen mode Exit fullscreen mode

dw

Define word:

dw 10
Enter fullscreen mode Exit fullscreen mode

dd

Define double word:

dd 10
Enter fullscreen mode Exit fullscreen mode

dq

Define quad word:

dq 10
Enter fullscreen mode Exit fullscreen mode

You will use these constantly when creating data structures, buffers, strings, tables, and other binary data.


Strings

A string can be defined using:

message db 'Hello, world!', 0
Enter fullscreen mode Exit fullscreen mode

The 0 is important when you want the string to be compatible with C-style null-terminated strings.

Memory might look like:

H e l l o ,   w o r l d !
48 65 6C 6C 6F ...
                     |
                     v
                     00
Enter fullscreen mode Exit fullscreen mode

The zero byte marks the end of the string.


Reserving Memory

FASM also provides declarations for reserving space.

For example:

buffer rb 100
Enter fullscreen mode Exit fullscreen mode

means:

Reserve 100 bytes.

Other declarations include:

rw
rd
rq
Enter fullscreen mode Exit fullscreen mode

These are useful when creating buffers and data structures.

For example:

buffer rb 4096
Enter fullscreen mode Exit fullscreen mode

creates a 4096-byte buffer.


Arithmetic

FASM supports the normal x86 arithmetic instructions.

For example:

mov rax, 20
add rax, 5
Enter fullscreen mode Exit fullscreen mode

Result:

RAX = 25
Enter fullscreen mode Exit fullscreen mode

Subtraction:

sub rax, 10
Enter fullscreen mode Exit fullscreen mode

Increment:

inc rax
Enter fullscreen mode Exit fullscreen mode

Decrement:

dec rax
Enter fullscreen mode Exit fullscreen mode

Negation:

neg rax
Enter fullscreen mode Exit fullscreen mode

You can also perform multiplication and division with:

mul
imul
div
idiv
Enter fullscreen mode Exit fullscreen mode

These instructions require more attention because some of them use implicit registers.


Bitwise Operations

Assembly programming also involves manipulating individual bits.

Important instructions include:

and
or
xor
not
shl
shr
Enter fullscreen mode Exit fullscreen mode

For example:

xor rax, rax
Enter fullscreen mode Exit fullscreen mode

is a very common instruction.

It sets:

RAX = 0
Enter fullscreen mode Exit fullscreen mode

because:

x XOR x = 0
Enter fullscreen mode Exit fullscreen mode

Comparing Values

The cmp instruction is extremely important.

For example:

cmp rax, rbx
Enter fullscreen mode Exit fullscreen mode

Conceptually, the CPU calculates:

RAX - RBX
Enter fullscreen mode Exit fullscreen mode

but does not store the result.

Instead, it updates CPU flags.

Those flags can then be used by conditional jumps.

For example:

cmp rax, rbx
je equal
Enter fullscreen mode Exit fullscreen mode

If the values are equal, execution jumps to:

equal:
Enter fullscreen mode Exit fullscreen mode

Control Flow

Assembly programs need branches and loops.

The basic unconditional jump is:

jmp label
Enter fullscreen mode Exit fullscreen mode

For example:

jmp start

start:
    ...
Enter fullscreen mode Exit fullscreen mode

Conditional jumps include:

je
jne
jg
jge
jl
jle
ja
jb
Enter fullscreen mode Exit fullscreen mode

These are based on CPU flags.

You will eventually learn the important difference between:

signed comparisons
Enter fullscreen mode Exit fullscreen mode

and:

unsigned comparisons
Enter fullscreen mode Exit fullscreen mode

because instructions such as jg and ja do not mean the same thing.


The Stack

The stack is one of the most important concepts in assembly.

The stack is an area of memory used for temporary data and function calls.

Two important registers are:

RSP
RBP
Enter fullscreen mode Exit fullscreen mode

RSP is the stack pointer.

RBP is commonly used as a frame pointer.

You can place a value onto the stack:

push rax
Enter fullscreen mode Exit fullscreen mode

and retrieve it:

pop rax
Enter fullscreen mode Exit fullscreen mode

Function calls also use the stack.

For example:

call my_function
Enter fullscreen mode Exit fullscreen mode

The CPU saves a return address and jumps to:

my_function:
Enter fullscreen mode Exit fullscreen mode

Eventually:

ret
Enter fullscreen mode Exit fullscreen mode

returns to the caller.

Understanding call, ret, push, pop, and rsp is essential for writing functions in assembly.


Functions

A function can be created manually:

my_function:
    mov rax, 123
    ret
Enter fullscreen mode Exit fullscreen mode

You can call it:

call my_function
Enter fullscreen mode Exit fullscreen mode

The general flow is:

caller
  |
  | call
  v
my_function
  |
  | ret
  v
caller
Enter fullscreen mode Exit fullscreen mode

At this level, you begin to understand how higher-level languages implement functions underneath.


Linux System Calls

One of the most interesting parts of Linux assembly programming is using system calls directly.

For x86-64 Linux, the syscall instruction enters the kernel.

For example:

mov rax, 60
mov rdi, 0
syscall
Enter fullscreen mode Exit fullscreen mode

This requests the exit system call.

The general idea is:

Your program
     |
     | syscall
     v
Linux kernel
     |
     v
Operating-system service
Enter fullscreen mode Exit fullscreen mode

System calls allow assembly programs to communicate directly with the operating system without necessarily going through the C standard library.


FASM Macros

One of FASM's most powerful features is its macro system.

Consider:

macro exit {
    mov rax, 60
    xor rdi, rdi
    syscall
}
Enter fullscreen mode Exit fullscreen mode

Now you can write:

exit
Enter fullscreen mode Exit fullscreen mode

instead of repeatedly writing:

mov rax, 60
xor rdi, rdi
syscall
Enter fullscreen mode Exit fullscreen mode

This becomes especially useful for larger assembly projects.

FASM provides several powerful macro-related mechanisms, including:

macro
match
rept
repeat
irp
irps
Enter fullscreen mode Exit fullscreen mode

These allow you to build abstractions and even create small domain-specific languages inside assembly.


Why Learn FASM Macros?

At first, assembly can become repetitive.

For example, imagine repeatedly writing system-call setup code.

Macros allow you to create your own abstractions.

You could eventually create something like:

print_string message, message_length
Enter fullscreen mode Exit fullscreen mode

which expands into the appropriate instructions.

The CPU still receives ordinary machine code.

The macro exists only during assembly.

Conceptually:

Your FASM source
       |
       v
     Macros
       |
       v
 Expanded assembly
       |
       v
 Machine code
Enter fullscreen mode Exit fullscreen mode

This is one of the reasons FASM is so interesting.


FASM Structures

FASM also allows you to define structures.

For example:

struc Person {
    .age dd ?
    .id  dd ?
}
Enter fullscreen mode Exit fullscreen mode

You can then use structure layouts to organize memory.

This becomes particularly useful when working with:

  • operating-system structures
  • executable formats
  • network packets
  • file formats
  • hardware structures
  • custom data structures

Instead of thinking only in individual variables, you can start thinking in terms of memory layouts.


Address Expressions

FASM supports powerful address expressions.

For example:

mov rax, [rbx + rcx*8]
Enter fullscreen mode Exit fullscreen mode

This can be understood as:

address = RBX + RCX * 8
Enter fullscreen mode Exit fullscreen mode

Then:

RAX = Memory[address]
Enter fullscreen mode Exit fullscreen mode

This type of addressing is extremely important for arrays.

Suppose each element is 8 bytes:

array[0] -> address + 0
array[1] -> address + 8
array[2] -> address + 16
array[3] -> address + 24
Enter fullscreen mode Exit fullscreen mode

Then:

mov rax, [rbx + rcx*8]
Enter fullscreen mode Exit fullscreen mode

can access an element based on RCX.


FASM and Executable Formats

One of the fascinating things about assembly programming is that you eventually discover that an executable is not simply "machine code."

An executable contains structures describing:

  • entry points
  • sections
  • memory permissions
  • imported libraries
  • relocation information
  • program headers
  • metadata

For example, Linux commonly uses:

ELF
Enter fullscreen mode Exit fullscreen mode

while Windows commonly uses:

PE
Enter fullscreen mode Exit fullscreen mode

FASM can construct these executable formats.

This makes FASM useful for learning not only assembly instructions, but also how executable files are structured.


What Can You Build With FASM?

FASM is not limited to tiny examples.

You can use it to build serious low-level projects.

For example:

Small programs

CLI tools
calculators
text utilities
file utilities
Enter fullscreen mode Exit fullscreen mode

Systems programming

bootloaders
kernel components
runtime libraries
system utilities
Enter fullscreen mode Exit fullscreen mode

Binary programming

ELF files
PE files
custom binary formats
binary parsers
Enter fullscreen mode Exit fullscreen mode

Educational projects

CPU experiments
memory allocators
custom data structures
assembly implementations of algorithms
Enter fullscreen mode Exit fullscreen mode

Advanced projects

operating-system components
compilers
virtual machines
debuggers
JIT systems
Enter fullscreen mode Exit fullscreen mode

The difficulty varies enormously, but FASM gives you the low-level control required for these types of projects.


A Good Way to Learn FASM

Don't try to memorize hundreds of instructions immediately.

A better approach is to build your understanding in layers.

Layer 1 — CPU Fundamentals

Learn:

Registers
RAX
RBX
RCX
RDX
RSI
RDI
RSP
RBP
R8-R15
RIP
RFLAGS
Enter fullscreen mode Exit fullscreen mode

Understand the difference between:

64-bit
32-bit
16-bit
8-bit
Enter fullscreen mode Exit fullscreen mode

Layer 2 — Basic Instructions

Learn:

mov
add
sub
inc
dec
neg
mul
imul
div
idiv
and
or
xor
not
shl
shr
Enter fullscreen mode Exit fullscreen mode

Layer 3 — Control Flow

Learn:

cmp
test
jmp
je
jne
jg
jge
jl
jle
Enter fullscreen mode Exit fullscreen mode

Then learn loops.


Layer 4 — Memory

Understand:

addresses
pointers
dereferencing
memory operands
arrays
structures
address expressions
Enter fullscreen mode Exit fullscreen mode

For example:

mov rax, [rbx + rcx*8]
Enter fullscreen mode Exit fullscreen mode

should eventually become completely natural to you.


Layer 5 — Stack and Functions

Learn:

push
pop
call
ret
rsp
rbp
stack frames
calling conventions
Enter fullscreen mode Exit fullscreen mode

This is where assembly begins to connect strongly with C.


Layer 6 — Operating System Interface

On Linux, learn:

syscall
system-call numbers
register conventions
file descriptors
read
write
open
close
exit
Enter fullscreen mode Exit fullscreen mode

Then build programs without relying on a C runtime.


Layer 7 — FASM Features

After understanding the CPU itself, learn FASM-specific features:

format
include
sections/segments
constants
data definitions
structures
macros
match
repeat
rept
irp
irps
Enter fullscreen mode Exit fullscreen mode

This is where you start taking advantage of FASM rather than treating it like a generic assembler.


FASM Is More Than "Writing Assembly"

The most important thing to understand is that learning FASM is not just about memorizing instructions.

You are learning several layers simultaneously:

                Your Program
                     |
                     v
              FASM source code
                     |
                     v
              FASM assembler
                     |
                     v
               Machine code
                     |
                     v
                 CPU / OS
                     |
                     v
                  Hardware
Enter fullscreen mode Exit fullscreen mode

As you become more comfortable, you will start seeing how all these layers connect.

A C program such as:

int x = 10;
x++;
Enter fullscreen mode Exit fullscreen mode

eventually becomes machine instructions.

An operating system eventually interacts with hardware through machine-level mechanisms.

An executable eventually becomes bytes arranged according to a binary format.

Assembly sits very close to that boundary.


Final Thoughts

Flat Assembler is an excellent choice if your goal is to understand computers at a low level.

It teaches you to think about:

  • registers
  • memory
  • addresses
  • instructions
  • CPU flags
  • stacks
  • functions
  • calling conventions
  • system calls
  • executable formats
  • machine code

And FASM's macro system gives you something especially interesting: the ability to build powerful abstractions while remaining very close to the machine.

If you are coming from C, FASM can also change the way you understand C itself.

Instead of seeing:

function();
Enter fullscreen mode Exit fullscreen mode

you begin asking:

How is the function called?
Where are the arguments?
Which registers contain them?
What happens to the stack?
Where does the return address go?
How is the return value produced?
Enter fullscreen mode Exit fullscreen mode

That is where assembly becomes truly valuable.

Don't learn assembly just to write assembly. Learn it to understand what your computer is actually doing.

And if you choose FASM, you get a particularly interesting tool for exploring that world.

Top comments (0)