Because why not? Software is fun.

I know this sounds insane; assembly for fun—but trust me, you feel alive.

I’m lucky to be at a point where I can build almost anything, so I’m testing the waters for a low-level series combining Node.js and assembly as a shared library.

In this article, we’re saying hello to the world in x86 Linux assembly.




Hello in Assembly

You need a Linux environment. On Windows, you can use WSL2.

Create a file called hello.s and add the following code:

.section  .data
msg:
    .asciz "Hello, World!\n"   # Define a null-terminated string with a newline
msg_len = . - msg              # Length from current location to msg

.section .text
.globl _start              # Declare _start as a global symbol (entry point)
_start:
    # --- sys_write(int fd, const void *buf, size_t count) ---
    movl $4, %eax              # sys_write into %eax
    movl $1, %ebx              # fd stdout into %ebx
    movl $msg, %ecx            # msg address
    movl $msg_len, %edx        # msg length
    int $0x80                  # Trigger the kernel system call

    # --- sys_exit(int status) ---
    movl $1, %eax              # sys_exit into %eax
    xorl %ebx, %ebx            # Zero out %ebx (exit code 0)
    int $0x80                  # Kernel call
Enter fullscreen mode

Exit fullscreen mode

Compile the program using as. Installing it on Linux is simple—just install the GNU tools with:

sudo apt update && sudo apt install -y build-essential gdb libgtk-3-dev
Enter fullscreen mode

Exit fullscreen mode

Then compile your assembly file:

as ./hello.s -o hello.o
Enter fullscreen mode

Exit fullscreen mode

This command creates the object file. Next, link it:

ld ./hello.o -o hello
Enter fullscreen mode

Exit fullscreen mode

Finally, run your program:

./hello
Enter fullscreen mode

Exit fullscreen mode

This might be the most complex “Hello World” you’ve ever seen, especially if you’re new to assembly.

Let’s break it down.




Sections in Assembly

Assembly programs are divided into sections:

  • .section .data → Where we store data, like “Hello, World!”
  • .section .text → Where we write instructions
  • .globl _start → Defines the program’s entry point



Symbols and Labels

Symbols are names for memory locations.

For example, msg is a symbol. It tells assembly, “Hey, remember this location—we’ll use it later.” It’s like a variable.

When you add a :, it becomes a label, defining the value of a symbol.

msg:
    .asciz "Hello, World!\n"  
Enter fullscreen mode

Exit fullscreen mode

This means: “At memory location msg, store the string ‘Hello, World!\n’.”

This pattern is everywhere. _start is also a label:

.globl _start  
_start:
Enter fullscreen mode

Exit fullscreen mode

Making _start global lets the assembler and linker find it.




Keeping Track of Memory

Assembly doesn’t track memory for you—you have to do it manually.



msg_len = . - msg

What’s happening here?

msg is just an address. It points to the start of "Hello, World!\n", but it doesn’t know where it ends.

. is the current memory location—right after the string.

So, msg_len = . - msg means:

Take the address after "Hello, World!\n" and subtract the start address.

That gives us the string’s length, which we need for the sys_write call.




CPU Architecture

CPU

Registers store data temporarily and operate way faster than RAM.

In our program, %eax is a register.



General-purpose registers:

  • %eax
  • %ebx
  • %ecx
  • %edx
  • %edi
  • %esi



Special-purpose registers:

Here’s how we move data between registers:

movl $4, %eax    # sys_write into %eax
movl $1, %ebx    # fd stdout into %ebx
movl $msg, %ecx  # msg address
movl $msg_len, %edx  # msg length
int $0x80        # Trigger the kernel system call
Enter fullscreen mode

Exit fullscreen mode

But before we can execute anything, we need to talk about…




The Kernel

The kernel is the middleman between your program and hardware. Every system call (file access, memory allocation, exiting a program) goes through the kernel.

Whenever you see this:

int $0x80  # Kernel call
Enter fullscreen mode

Exit fullscreen mode

You’re asking the kernel for help.

But the kernel has rules. Before calling it, you must set up registers with the correct values.

For example, to exit a program, the kernel expects:

movl $1, %eax  # sys_exit command
movl $0, %ebx  # Exit status
Enter fullscreen mode

Exit fullscreen mode

The sys_exit call expects:

  • %eax = 1 → “I want to exit.”
  • %ebx = 0 → “Exited successfully.”

It’s the same as returning 0 in C:

int main() {
    return 0;  // Exit status 0 (success)
}
Enter fullscreen mode

Exit fullscreen mode




Writing to the Console

Unlike sys_exit, writing to the console requires more information:

movl $4, %eax    # sys_write command
movl $1, %ebx    # File descriptor 1 (stdout)
movl $msg, %ecx  # Message address
movl $msg_len, %edx  # Message length
int $0x80        # Kernel call
Enter fullscreen mode

Exit fullscreen mode

Here’s what’s happening:

  1. movl $4, %eax → The number 4 tells the kernel, “I want to write.”
  2. movl $1, %ebx → The number 1 means “write to stdout.”
  3. movl $msg, %ecx → Where’s the message? It’s at msg.
  4. movl $msg_len, %edx → How long is the message? That’s in msg_len.
  5. int $0x80 → Call the kernel to execute the write.

Once we’ve written our message, we exit:

movl $1, %eax  # sys_exit
xorl %ebx, %ebx  # Exit code 0
int $0x80  # Kernel call
Enter fullscreen mode

Exit fullscreen mode




AT&T vs. Intel Syntax

This program uses AT&T syntax, which is common in GNU assemblers.

Intel syntax, used in NASM, looks slightly different. A major difference is that AT&T syntax prefixes registers with %.

Some people prefer Intel syntax, but most books stick with AT&T, so it’s worth learning.




You Made It!

That’s it! You just wrote and understood x86 assembly’s “Hello, World!”

I might start a low-level Node.js series soon. Let me know if that sounds interesting!

You can find me on x

Meanwhile, check out this series where we build a Node-native message broker from scratch:

favicon
payhip.com

Here’s what you’ll learn:

  • Long live TCP with heartbeat functionality
  • Buffers
  • Queue management: acknowledgments and cleanup
  • A client driver based on events and event queues
  • Pub/Sub
  • BSON serialization and deserialization for durable queues
  • Handshake and authentication 🚀



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *