Skip to content

04 — Setting Up the Environment

Before writing assembly, you need an assembler, linker, and debugger installed and configured.


Required Tools

Tool Purpose
nasm Netwide Assembler — converts .asm.o
ld GNU Linker — converts .o → executable
gdb GNU Debugger — step through assembly
objdump Disassemble and inspect object files
strace Trace system calls at runtime
readelf Inspect ELF file headers and sections

Installation

Ubuntu / Debian / WSL2

sudo apt update
sudo apt install nasm binutils gdb

Fedora / RHEL

sudo dnf install nasm binutils gdb

Arch Linux

sudo pacman -S nasm binutils gdb

macOS (Homebrew — note: macOS uses Mach-O, not ELF)

brew install nasm

macOS uses different syscall numbers and binary format. Most examples in this series target Linux ELF. Use WSL2 or a Linux VM for full compatibility.

wsl --install          # enables WSL2 with Ubuntu
Then install tools inside the WSL2 terminal as above.


Verify Installation

nasm --version     # NASM version 2.15.x
ld --version       # GNU ld (Binutils) 2.38
gdb --version      # GNU gdb 12.x
objdump --version  # GNU objdump 2.38

Project Directory Structure

Organize your assembly projects:

asm-projects/
├── 01-hello/
│   ├── hello.asm
│   └── Makefile
├── 02-registers/
│   ├── registers.asm
│   └── Makefile
└── lib/
    └── print.asm   (reusable routines)

Your First Program: Hello, World!

Create hello.asm:

; hello.asm — print "Hello, World!" to stdout
; Build: nasm -f elf64 hello.asm -o hello.o && ld hello.o -o hello

section .data
    msg db "Hello, World!", 10  ; string + newline (0x0A)
    len equ $ - msg             ; length = current address minus msg address

section .text
    global _start               ; entry point must be exported

_start:
    ; sys_write(fd=1, buf=msg, count=len)
    mov rax, 1      ; syscall number: sys_write
    mov rdi, 1      ; file descriptor: 1 = stdout
    mov rsi, msg    ; pointer to string
    mov rdx, len    ; number of bytes to write
    syscall         ; invoke the kernel

    ; sys_exit(status=0)
    mov rax, 60     ; syscall number: sys_exit
    mov rdi, 0      ; exit status 0 = success
    syscall

Assemble and Run

nasm -f elf64 hello.asm -o hello.o    # assemble
ld hello.o -o hello                    # link
./hello                                # run
Hello, World!

Inspect the Object File

objdump -d hello.o         # disassemble
objdump -D hello           # disassemble all sections
readelf -h hello           # ELF header
readelf -S hello           # section headers

Reusable Makefile

# Makefile for NASM x86-64 Linux programs
NAME = hello

$(NAME): $(NAME).o
    ld $< -o $@

$(NAME).o: $(NAME).asm
    nasm -f elf64 $< -o $@

clean:
    rm -f $(NAME).o $(NAME)

run: $(NAME)
    ./$(NAME)

.PHONY: clean run

Usage:

make        # build
make run    # build and run
make clean  # remove build artifacts


Debugging with GDB

GDB lets you step through assembly instruction by instruction.

# Compile with debug info (DWARF)
nasm -f elf64 -g -F dwarf hello.asm -o hello.o
ld hello.o -o hello

# Launch GDB
gdb ./hello

Essential GDB Commands

(gdb) set disassembly-flavor intel    # use Intel syntax
(gdb) break _start                    # set breakpoint at entry
(gdb) run                             # start the program
(gdb) info registers                  # show all register values
(gdb) info registers rax rdi rsi      # show specific registers
(gdb) stepi                           # execute ONE instruction (si)
(gdb) nexti                           # step over calls (ni)
(gdb) x/16xb $rsp                     # examine 16 bytes at RSP (hex)
(gdb) x/4xg $rsp                      # examine 4 qwords at RSP
(gdb) x/s $rsi                        # examine memory as string
(gdb) disassemble _start              # show disassembly of function
(gdb) display/i $rip                  # auto-show next instruction
(gdb) quit                            # exit GDB

GDB TUI Mode (Text UI)

gdb -tui ./hello

Or press Ctrl+X A inside GDB to toggle the split view showing source/disassembly and registers side by side.


Useful objdump Commands

# Disassemble the .text section with Intel syntax
objdump -d -M intel hello

# Show all sections with content
objdump -D -M intel hello

# Show section headers
objdump -h hello

# Show symbol table
objdump -t hello

# Dump hex + ASCII of .data section
objdump -s -j .data hello

strace — Trace System Calls

strace ./hello

Output:

write(1, "Hello, World!\n", 14)        = 14
exit(0)                                = ?

This confirms exactly which syscalls your program makes — invaluable for debugging.


NASM Flags Reference

Flag Meaning
-f elf64 Output format: 64-bit ELF (Linux)
-f elf32 Output format: 32-bit ELF
-f macho64 Output format: 64-bit Mach-O (macOS)
-g Include debug information
-F dwarf Use DWARF debug format
-l hello.lst Generate listing file
-o output.o Specify output filename
-E Preprocess only (expand macros)
-w+all Enable all warnings

Common Errors

Error Cause Fix
error: label or instruction expected Missing section declaration Add section .text
ld: cannot find entry symbol _start Missing global _start Add global _start
Segmentation fault Jumped to invalid address Check RIP with GDB
nasm: error: invalid combination of opcode and operands Wrong register size Use matching register names

Next: 05 — Registers