Advanced

Hello World in Assembly

Your first Assembly program - the classic Hello World example with Docker setup using NASM

Assembly language brings you as close to the hardware as possible while still using human-readable mnemonics. This Hello World example uses x86 assembly with NASM (Netwide Assembler) syntax, running on Linux.

The Code

Create a file named hello.asm:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
section .data
    message db "Hello, World!", 10
    msglen equ $ - message

section .text
    global _start

_start:
    mov eax, 4          ; sys_write system call number
    mov ebx, 1          ; file descriptor 1 (stdout)
    mov ecx, message    ; pointer to message
    mov edx, msglen     ; message length
    int 0x80            ; invoke kernel

    mov eax, 1          ; sys_exit system call number
    xor ebx, ebx        ; exit code 0
    int 0x80            ; invoke kernel

Understanding the Code

Data Section

1
2
3
section .data
    message db "Hello, World!", 10
    msglen equ $ - message
  • section .data - Declares the data segment for initialized data
  • message db - Defines a byte string; db means “define bytes”
  • "Hello, World!", 10 - The string followed by ASCII 10 (newline character)
  • msglen equ $ - message - Calculates string length; $ is current position, so $ - message gives the byte count

Text Section

1
2
section .text
    global _start
  • section .text - Declares the code segment (executable instructions)
  • global _start - Makes _start visible to the linker; this is where execution begins on Linux

The Write System Call

1
2
3
4
5
6
_start:
    mov eax, 4          ; sys_write system call number
    mov ebx, 1          ; file descriptor 1 (stdout)
    mov ecx, message    ; pointer to message
    mov edx, msglen     ; message length
    int 0x80            ; invoke kernel

This is a Linux system call using the 32-bit calling convention:

RegisterPurposeValue
eaxSystem call number4 (sys_write)
ebxFile descriptor1 (stdout)
ecxBuffer pointeraddress of message
edxByte countlength of message

int 0x80 triggers a software interrupt, transferring control to the Linux kernel to perform the write operation.

The Exit System Call

1
2
3
    mov eax, 1          ; sys_exit system call number
    xor ebx, ebx        ; exit code 0
    int 0x80            ; invoke kernel
  • mov eax, 1 - System call 1 is sys_exit
  • xor ebx, ebx - Sets ebx to 0 (exit code); XOR with itself is a common assembly idiom for zeroing a register
  • int 0x80 - Invokes the kernel to terminate the program

Without this exit call, execution would continue past our code into undefined memory, likely causing a segmentation fault.

Running with Docker

The easiest way to run x86 assembly without setting up a local toolchain:

1
2
3
4
5
# Pull the NASM assembly image
docker pull esolang/x86asm-nasm:latest

# Assemble, link, and run
docker run --rm -v $(pwd):/code -w /code esolang/x86asm-nasm /bin/x86asm-nasm hello.asm

The Docker image handles:

  1. Assembling the .asm file with NASM
  2. Linking the object file with ld
  3. Executing the resulting binary

Running Locally

If you have NASM and a linker installed:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# On Linux (32-bit mode)
nasm -f elf32 -o hello.o hello.asm
ld -m elf_i386 -o hello hello.o
./hello

# On Linux (64-bit system running 32-bit code)
# May need to install 32-bit libraries first
sudo apt-get install libc6-dev-i386  # Debian/Ubuntu
nasm -f elf32 -o hello.o hello.asm
ld -m elf_i386 -o hello hello.o
./hello

Expected Output

Hello, World!

Key Concepts

Registers

x86 processors have general-purpose registers. In 32-bit mode:

RegisterTraditional Use
eaxAccumulator, return values, syscall numbers
ebxBase pointer, first syscall argument
ecxCounter, second syscall argument
edxData, third syscall argument

System Calls

System calls are how programs request services from the operating system kernel:

  • Write to screen: syscall 4 (sys_write)
  • Read from keyboard: syscall 3 (sys_read)
  • Exit program: syscall 1 (sys_exit)
  • Open file: syscall 5 (sys_open)

The int 0x80 instruction triggers the kernel to handle the request.

Memory Layout

Assembly programs have distinct sections:

SectionContents
.dataInitialized global variables
.bssUninitialized global variables
.textExecutable code

Why _start Instead of main?

In C programs, main() is called by startup code that the C runtime provides. In pure assembly without the C library, we use _start as the entry point that the linker expects.

Common Pitfalls

  1. Forgetting to exit: Without sys_exit, the program crashes after your code
  2. Wrong register sizes: Using ax when you mean eax on 32-bit systems
  3. Missing global _start: The linker can’t find the entry point
  4. Incorrect syscall numbers: Different architectures and ABIs use different numbers

Going Further

This example uses 32-bit x86 assembly with Linux syscalls. Modern systems often use:

  • 64-bit mode: Different registers (rax, rdi, rsi) and syscall convention (syscall instead of int 0x80)
  • C library: Call printf and exit instead of raw syscalls
  • Different platforms: Windows uses different calling conventions entirely

Assembly is the foundation that all other languages build upon. Understanding it gives you insight into what your code really does at the hardware level.

Running Today

All examples can be run using Docker:

docker pull esolang/x86asm-nasm:latest
Last updated: