Your First Assembly Program
The traditional first program in any language prints a greeting and exits, and assembly is no exception — but writing it forces you to confront details a high-level language normally hides entirely, such as exactly how a program talks to the operating system. On Linux x86-64, a program requests OS services through system calls: placing a syscall number in RAX, arguments in RDI, RSI, RDX (and further registers as needed), then executing the syscall instruction, which transfers control to the kernel and returns with a result in RAX. Printing text requires the write syscall (number 1), which needs a file descriptor (1 for standard output) in RDI, a pointer to the bytes to print in RSI, and the byte count in RDX; exiting cleanly requires the exit syscall (number 60), which needs only an exit code in RDI. There is no print() function to call — you are directly issuing the same low-level request that every print statement in every higher-level language eventually boils down to.
Cricket analogy: A syscall is like a fielder radioing the umpire directly with a specific coded request ('DRS review, code 3') rather than going through a translator — you specify exactly the request number and details, and the umpire (kernel) handles it.
Anatomy of the Program
A minimal NASM program is organized into sections that tell the assembler and linker how to lay out the final binary. The .data section holds initialized values known at assembly time — a string like msg db 'Hello, Assembly!', 10 stores the literal bytes of the message plus a trailing newline (byte value 10), and equ msg_len, $ - msg computes the string's length automatically by subtracting the label's address from the current position ($). The .text section holds actual instructions, and by Linux convention the label _start marks the program's true entry point — not main, which is a C runtime convention; the linker needs global _start so this symbol is visible outside the file and can be used as the entry point when producing the executable. Every instruction in .text executes strictly in order unless a jump, call, or syscall return redirects control, which is why the exit syscall must appear as literally the last thing the program does — without it, execution would fall off the end of _start into whatever bytes happen to follow in memory, causing a crash.
Cricket analogy: The .data section is like the pre-match team sheet listing fixed facts (playing XI, toss result) decided before a ball is bowled, while .text is the live over-by-over action — data is prepared upfront, code executes step by step afterward.
Assembling, Linking, and Running It
Producing a runnable program from the source requires the two-stage toolchain covered elsewhere in this course: nasm -f elf64 hello.asm -o hello.o assembles the source into an ELF object file, encoding each mnemonic into its machine-code bytes and recording the _start symbol so it's visible to the linker; ld hello.o -o hello then links that single object file into a final executable, since this minimal program makes no external library calls and therefore has no unresolved symbols to worry about. Running ./hello executes the binary directly — the Linux kernel's ELF loader maps the file's sections into a fresh process's address space, sets RIP to the _start label's address, and execution begins exactly where you wrote it to begin. Checking echo $? immediately afterward reveals the process's exit code, which is precisely the value you placed in RDI right before the exit syscall — a concrete, observable confirmation that the assembly you wrote is genuinely controlling the CPU end to end, with no runtime or interpreter standing between your instructions and the hardware.
Cricket analogy: Assembling then linking a single file is like a solo net session that needs no team coordination — no other players' schedules to sync, unlike a full XI's linked training plan, because this program has zero external dependencies.
; hello.asm - a minimal x86-64 Linux assembly program (NASM syntax)
section .data
msg db 'Hello, Assembly!', 10 ; the string bytes plus newline (10 = '\n')
msg_len equ $ - msg ; length computed automatically
section .text
global _start ; entry point visible to the linker
_start:
; write(1, msg, msg_len)
mov rax, 1 ; syscall number 1 = write
mov rdi, 1 ; file descriptor 1 = stdout
mov rsi, msg ; pointer to the bytes to print
mov rdx, msg_len ; number of bytes to print
syscall
; exit(0)
mov rax, 60 ; syscall number 60 = exit
mov rdi, 0 ; exit code 0 = success
syscallBuild and run it with: nasm -f elf64 hello.asm -o hello.o && ld hello.o -o hello && ./hello && echo $? — you should see 'Hello, Assembly!' printed followed by an exit code of 0.
- On Linux x86-64, programs request OS services via syscalls: number in RAX, arguments in RDI/RSI/RDX, triggered by the syscall instruction.
- The write syscall (1) needs a file descriptor, buffer pointer, and byte count; exit (60) needs only an exit code.
- The .data section holds initialized values known at assembly time; equ can compute derived constants like string length automatically.
- The .text section holds instructions; _start (not main) is the true Linux entry point and must be declared global.
- Execution must end with an explicit exit syscall, or the CPU will run past _start into undefined memory and crash.
- nasm -f elf64 assembles the source; ld links the resulting object file into a runnable executable.
- echo $? after running the program reveals the exact exit code placed in RDI before the exit syscall.
Practice what you learned
1. What must be placed in the RAX register before executing the syscall instruction on Linux x86-64?
2. Why must a minimal hand-written assembly program explicitly call the exit syscall at the end?
3. What does 'equ msg_len, $ - msg' accomplish in NASM?
4. Why is _start, not main, used as the entry label in raw x86-64 Linux assembly?
Was this page helpful?
You May Also Like
The Assembly Toolchain (Assembler, Linker)
How assembly source files travel from text to a runnable executable via an assembler, object files, and a linker.
CPU Registers Explained
A practical guide to what CPU registers are, the main categories on x86-64, and how assembly instructions use them to do work.
What Is Assembly Language?
An introduction to assembly language as the human-readable face of machine code, and why understanding it still matters for serious programmers.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics