Skip to content

Kynatium-Labs/hello_world

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🖥️ Hello World — A Low-Level Approach

Strip away every abstraction. See what really happens when a program runs.

Platform Language Assembler Linker License Arch


📖 What This Repository Teaches

Most "Hello World" examples hide what actually happens.
This series removes those layers one by one.

  ┌──────────────────────────────────────────────────────┐
  │                   Your Program                       │
  │                                                      │
  │   printf("Hello")    ← Part 1 (high-level C)        │
  │        │                                             │
  │        ▼                                             │
  │   write(1, msg, 14)  ← Part 2 (C without printf)    │
  │        │                                             │
  │        ▼                                             │
  │   mov rax, 1         ← Part 3 & 4 (pure assembly)   │
  │   syscall                                            │
  │        │                                             │
  │        ▼                                             │
  │  ┌───────────┐                                       │
  │  │  KERNEL   │  → writes to stdout (fd 1)            │
  │  └───────────┘                                       │
  └──────────────────────────────────────────────────────┘

You will learn: Kernel vs user space · file descriptors · exit codes · Linux syscalls · how C maps to assembly


🗂️ Project Structure

hello_world/
├── 📄 README.md
├── 📄 LICENSE
├── 📄 resources.txt
└── 📁 code/
    ├── hello_w_printf.c      ← C with printf (the familiar way)
    ├── hello_wo_printf.c     ← C with write() (closer to the OS)
    └── hello_world.asm       ← Pure x86-64 assembly (nothing hidden)

🧭 Series Structure

Part 1 — How Programs Talk to the OS

Concepts only — no code yet.

Introduces the core ideas that underpin everything:

Concept What You'll Learn
Kernel vs User Space Where your code actually runs
File Descriptors 0 = stdin · 1 = stdout · 2 = stderr
Exit Codes How the OS knows if your program succeeded
System Calls The only way to talk to the kernel

Part 2 — Hello World in C (Closer to the OS)

📄 See: code/hello_w_printf.c and code/hello_wo_printf.c

We move from printf() to write() — removing the C standard library layer:

  printf("Hello")           write(1, "Hello\n", 14)
       │                            │
       ▼                            ▼
  ┌──────────┐              ┌──────────────┐
  │ C stdlib │ ──────────►  │  direct fd   │
  │ buffered │              │  unbuffered  │
  └──────────┘              └──────────────┘
       │                            │
       └──────────┬─────────────────┘
                  ▼
          ┌──────────────┐
          │  sys_write   │
          │  (kernel)    │
          └──────────────┘

Both end up in the same syscall — but the second version shows it explicitly.


Part 3 — Assembly Basics

Key concepts before writing assembly:

Mnemonic Meaning Purpose
mov Move Load a value into a register
db Define Byte Declare raw data (strings)
global Global Export a symbol for the linker
syscall System Call Trap into the kernel
xor Exclusive OR Fast way to zero a register

Syscall calling convention (x86-64 Linux):

  ┌──────────┬──────────────────────────────┐
  │ Register │ Role                         │
  ├──────────┼──────────────────────────────┤
  │ rax      │ syscall number               │
  │ rdi      │ 1st argument                 │
  │ rsi      │ 2nd argument                 │
  │ rdx      │ 3rd argument                 │
  └──────────┴──────────────────────────────┘

  sys_write = 1    →  write(fd, buf, count)
  sys_exit  = 60   →  exit(code)

Part 4 — Full Hello World in Assembly

📄 See: code/hello_world.asm

Everything comes together. The full path:

  ┌────────────┐    ┌────────────┐    ┌────────────┐    ┌────────────┐
  │   idea     │───▶│ registers  │───▶│  kernel    │───▶│  output    │
  │            │    │            │    │            │    │            │
  │ "print     │    │ rax = 1    │    │ sys_write  │    │ Hello,     │
  │  hello"    │    │ rdi = 1    │    │ executes   │    │ World!     │
  │            │    │ rsi = msg  │    │            │    │            │
  └────────────┘    └────────────┘    └────────────┘    └────────────┘

Every register is set by hand. No C runtime. No libraries. Just you and the kernel.


⚙️ Requirements

Tool Purpose
🐧 Linux x86-64 Operating system
🔧 NASM Netwide Assembler
🔗 ld GNU linker

🚀 How to Build and Run

# Assemble
nasm -f elf64 code/hello_world.asm -o hello.o

# Link
ld hello.o -o hello

# Run
./hello

Expected output:

Hello, World!

🎯 Who This Is For

✅ You want to understand what really happens when a program runs
✅ You want to learn Linux system calls from the ground up
✅ You want to bridge the gap between C and assembly
✅ You want strong low-level fundamentals

❌ This is not for you if you only want quick results.


🧬 Bonus: From Instruction to Binary

Ever wonder what the CPU actually sees when you write this?

mov rax, 1

The CPU doesn't understand English words like mov or register names like rax.
Every instruction and register has a numeric code defined in the Instruction Set Architecture (ISA) — a specification published by the CPU manufacturer (Intel/AMD) that assigns a unique number to every operation and register the chip supports.


Step 1 — Look up the numeric codes

Suppose we look up the ISA and find:

Component Name Numeric Code (decimal)
Opcode (instruction) mov 2
Register rax 1
Immediate (value) 1 1

📝 These are simplified for illustration. Real x86-64 encoding is more complex, but the principle is the same — every part of an instruction becomes a number.

So mov rax, 1 is really just:

  instruction = 2,  register = 1,  value = 1

Step 2 — Convert to Hexadecimal

Each of those decimal numbers maps directly to hex:

  Decimal          Hex
  ───────────────────────
  2    ──────►    0x02        (mov)
  1    ──────►    0x01        (rax)
  1    ──────►    0x01        (immediate value)

Lined up, the instruction in hex becomes:

  ┌──────┬──────┬──────┐
  │  02  │  01  │  01  │
  │ mov  │ rax  │  1   │
  └──────┴──────┴──────┘

Step 3 — Convert to Binary

Now convert each hex byte to its 8-bit binary form:

  Hex              Binary
  ────────────────────────────
  0x02  ──────►   00000010      (mov)
  0x01  ──────►   00000001      (rax)
  0x01  ──────►   00000001      (immediate value)

The full instruction as the CPU sees it:

  ┌──────────┬──────────┬──────────┐
  │ 00000010 │ 00000001 │ 00000001 │
  │   mov    │   rax    │    1     │
  └──────────┴──────────┴──────────┘

The Full Journey

  ┌─────────────────────────────────────────────────────────────────┐
  │                                                                 │
  │  ASSEMBLY       mov          rax          1                     │
  │  (what we write)                                                │
  │                  │            │            │                     │
  │                  ▼            ▼            ▼                     │
  │  DECIMAL         2            1            1                    │
  │  (ISA lookup)                                                   │
  │                  │            │            │                     │
  │                  ▼            ▼            ▼                     │
  │  HEX            02           01           01                    │
  │                  │            │            │                     │
  │                  ▼            ▼            ▼                     │
  │  BINARY      00000010     00000001     00000001                 │
  │  (what the CPU actually sees)                                   │
  │                                                                 │
  └─────────────────────────────────────────────────────────────────┘

💡 Key insight: the human-readable instruction mov rax, 1 is just a convenience.
The assembler (NASM) translates it into raw numbers. The CPU reads those numbers as electrical signals — nothing else. No names, no syntax, just voltage levels representing 0 and 1.


📚 Resources


If high-level code feels like magic, this series shows you the machinery behind it.

No abstractions · No shortcuts · Just the system

About

A low-level walkthrough of Hello World, covering kernels, assembly, and how high-level code maps to binary.

Topics

Resources

License

Stars

Watchers

Forks

Contributors