“Hello, World” in NASM x64 – Explained Line by Line

This article is aimed at beginners who have a basic understanding of a high-level programming language like C, Java, or Python. The key to understanding the article is to be patient as you read through and try out the code.
No fluff. Let’s go!
The following code is an example of printing “Hello World!” to the screen:
section .data
msg db "Hello World!", 0, 10 ; String with null (0) and newline (10)
msglength equ $ - msg ; msglength = len(msg) i.e calculates length of string
section .text
global _start ; entry point of program
_start:
; Print msg
mov rax, 1 ; sys_write
mov rdi, 1 ; file descriptor: stdout
mov rsi, msg ; pointer to msg
mov rdx, msglength ; length of msg
syscall ; ask kernel to print msg
_exit:
mov rax, 60 ; sys_exit
mov rdi, 0 ; return value 0 akin to exit(0) in C or System.exit(0) in Java
syscall ; please kernel, exit nicely
Running the code
Save the above code as helloworld.asm then run the following in the terminal
nasm -f elf64 -g -F dwarf helloworld.asm -o helloworld
ld -o helloworld helloworld.o
./helloworld
Explanation
A typical nasm code is divided into three sections:
section .data : A section for declaring initialized data or constants.
- Think of it like "int age=42;” in C or Java.
section .bss : A section for declaring uninitialized data. (not shown in the code above)
- Think of it like “int age;” in C or Java without definite declaration. Space is reserved but no value is set yet.
section .text : The entry point of our nasm program.
- Think of it like “main” function in C or Java. It is called at the program startup.
The line, msg db “Hello World!”, 0, 10 is similar to
msg = "Hello World!"
in Python but unlike Python, one needs to directly tell the msg string to end and go to the next line. That is why zero (0), and ten (10) are appended to the msg string.
0 is called a null terminator which says “the string ends here”
10 says let’s go to a new line
The code excerpt:
_start:
; Print msg
mov rax, 1 ; sys_write
mov rdi, 1 ; file descriptor: stdout
mov rsi, msg ; pointer to msg
mov rdx, msglength ; length of msg
syscall ; ask kernel to print msg
basically says print msg to screen.
Think of it like a front desk staff of a hotel going into a couple of rooms called rax, rdi, rsi, and rdx.
The staff walks into each room to check what's written inside:
🛏️ Room
rax
: It says 1 → “Ah, this means it’s a write request.”🛏️ Room
rdi
: It also says 1 → “So we’re writing to stdout—the screen.”🛏️ Room
rsi
: Points to a note left on the table → it’s the msg, saying"Hello World!"
.🛏️ Room
rdx
: Says msglength → “That’s how many bytes to send.”
After checking all rooms, the staff says:
“Got it! I’m supposed to write ‘Hello World!’ to the screen. Let’s do this.”
They then press a button labeled syscall
—and the message is delivered.
Then later, the staff walks back into:
rax = 60
→ “Oh, now it’s an exit request.”rdi = 0
→ “Exit with code 0 (success).”
And again, presses syscall
—and the program gracefully exits the hotel.
Note: Anything after a semi-colon (;) is a comment.
Subscribe to my newsletter
Read articles from Rawley Amankwah directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
