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

Rawley AmankwahRawley Amankwah
3 min read

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:

  1. section .data : A section for declaring initialized data or constants.

    • Think of it like "int age=42;” in C or Java.
  2. 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.
  3. 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 dbHello 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.

0
Subscribe to my newsletter

Read articles from Rawley Amankwah directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rawley Amankwah
Rawley Amankwah